# Field types Source: https://developers.fibery.com/api-reference/field-types Browse primitive Field types supported by Fibery. Work in progress. This page will document each primitive Field type with accepted shapes, validation rules, and meta flags. Primitive Field types accepted by `schema.field/create` and returned by `fibery.entity/query`. | Field type | Example | Comments | | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- | | `fibery/int` | `42` | | | `fibery/decimal` | `0.33` | | | `fibery/bool` | `true` | | | `fibery/text` | `Don't panic` | Up to 1k characters. Can be styled using `ui/type` meta flag: `text` \| `email` \| `phone` \| `url` | | `~~fibery/email~~` | [~~contact@megadodo.com~~](mailto:contact@megadodo.com) | Deprecated. Use a `fibery/text` field with `ui/type` meta set to `"email"`: `{"ui/type": "email"}` | | `fibery/emoji` | 🏏 | | | `fibery/date` | `1979-10-12` | | | `fibery/date-time` | `2019-06-24T12:25:20.812Z` | | | `fibery/date-range` | `{"start": "2019-06-27", "end": "2019-06-30"}` | | | `fibery/date-time-range` | `{"start": "2019-06-18T02:40:00.000Z", "end": "2019-07-25T11:40:00.000Z"}` | | | `fibery/location` | `{"longitude": 2.349606, "latitude": 48.890764, "fullAddress": "MΓ©tro Marcadet Poissonniers, 67 boulevard BarbΓ¨s, Paris, 75018, France", "addressParts": {"city": "Paris", "country": "France"}}` | All address parts are optional. | | `fibery/uuid` | `acb5ef80-9679-11e9-bc42-526af7764f64` | | | `fibery/rank` | `1000` | | | `fibery/json-value` | `{"paranoid?": true}` | | For non-primitive types (single-select, multi-select, entity relations, rich text) see [Fields](/guides/http-api/fields). # MCP tool reference (full) Source: https://developers.fibery.com/api-reference/mcp-tool-reference Deep reference for every Fibery MCP Server tool: parameters, value formats, examples. This reference is for the nerds who want to know all the internals. ## Common Concepts **Database name** β€” Always `"Space/Database"` format, e.g. `"SoftDev/bug"`, `"Product Management/feature"`. Names are case-sensitive. **Field name** β€” Always `"Space/FieldName"` format. **The space prefix does not necessarily match the database's own space.** Fields inherited from system apps use their own prefixes: `user/name`, `assignments/assignees`, `workflow/state`, `comments/comments`, `icon/icon`. Always derive field names from `schema_detailed` output, never guess them. **`fibery/id`** β€” Internal UUID for an entity (e.g., `"5766cc9a-ae80-4893-82d3-db8b78fdfa13"`). Required for updates, state changes, and document operations. Retrieved via `query`. **`fibery/public-id`** β€” Human-readable string ID (e.g., `"13961"`). Used in web URLs and `get_entity_links`. **System fields** β€” Auto-populated and read-only: `fibery/id`, `fibery/public-id`, `fibery/creation-date`, `fibery/modification-date`, `fibery/created-by`, `fibery/modified-by`. **Formula fields** β€” Always read-only. Identified by the `formula:` comment in `schema_detailed` output. **Typical sequence for entity work** β€” `schema` β†’ `schema_detailed` β†’ `query` (to get IDs) β†’ write tools. ## Workspace & Schema ### `schema` Returns the high-level structure of the workspace: all spaces and their databases, without field details. **Parameters** β€” None. **Returns** β€” All spaces and database names in the workspace. **Example output (excerpt)** ```javascript theme={null} space SoftDev { database SoftDev/Commit database SoftDev/Deployment database SoftDev/Dev Task database SoftDev/bug } space Product Management { database Product Management/Insight database Product Management/Product Area database Product Management/feature } ``` **Note** β€” Call this first before any other tool. It is the source of truth for valid database names. ### `schema_detailed` Returns field definitions and enum values for specific databases in YAML format. **Parameters** | Name | Type | Required | Description | | ------------------------- | --------- | -------- | ------------------------------------------------------------------ | | `databases` | string\[] | Yes | Database names in `"Space/Database"` format | | `includeRelatedDatabases` | boolean | No | Also include related databases with their fields. Default: `false` | **Returns** β€” Per-database YAML with field names, types, and metadata comments: `read-only`, `formula`, `collection`, `UI title`, `default`. **Example** ```json theme={null} { "databases": [ "SoftDev/bug" ], "includeRelatedDatabases": false } ``` **Example output (excerpt)** ```yaml theme={null} SoftDev/bug: # Bug is an issue in a product. fields: fibery/id: fibery/uuid # read-only fibery/public-id: fibery/text # read-only fibery/creation-date: fibery/date-time # read-only; default: $now user/name: fibery/text # UI title user/Owner: fibery/user # default: $my-id user/urgent: fibery/bool user/Product Area: Product Management/Product Area user/feature: Product Management/feature SoftDev/Regression: fibery/bool SoftDev/Created By AI: fibery/bool SoftDev/Dev Task: SoftDev/Dev Task assignments/assignees: fibery/user # collection workflow/state: workflow/state_SoftDev/bug # default: Icebox comments/comments: comments/comment # collection ``` **Key reading rules for this output** * Fields marked `read-only` or `formula:` cannot be written. * Fields marked `collection` require `add_collection_items` / `remove_collection_items` to modify. * `workflow/state` fields require `set_state` to modify. * `Collaboration~Documents/Document` fields require `set_document_content` or `append_document_content`. * The field name as shown (including its space prefix) is exactly what goes in `q/select`, `update_entities`, etc. ### `get_me` Returns information about the currently authenticated user. **Parameters** β€” None. **Returns** β€” `fibery/id`, `user/name`, `user/email`, `fibery/role`, `fibery/admin?`. **Example output** ```json theme={null} { "fibery/admin?": true, "user/email": "michael@fibery.io", "fibery/role": "role/admin", "fibery/id": "0000000", "user/name": "Michael Dubakov" } ``` ## Querying & Searching ### `query` Executes a Fibery Query API command against any database. Supports field selection, filtering, sorting, pagination, sub-queries, and aggregation. **Parameters** | Name | Type | Required | Description | | -------- | ------ | -------- | ----------------------------------------------------------------------------- | | `query` | object | Yes | Query definition (see below) | | `params` | object | No | Parameter values referenced in `q/where` via `$param` syntax. Default: `\{\}` | **`query` object fields** | Field | Type | Required | Description | | ------------ | ------------------- | -------- | --------------------------------------------- | | `q/from` | string | Yes | Source database, e.g. `"SoftDev/bug"` | | `q/select` | object \| string\[] | Yes | Fields to retrieve | | `q/where` | array | No | Filter expression | | `q/order-by` | array | No | Sort criteria | | `q/limit` | number | No | Results per page. Default: `100`. Max: `1000` | | `q/offset` | number | No | Results to skip for pagination. Default: `0` | **Field selection β€” basic** ```json theme={null} { "Name": "user/name" } ``` **Field selection β€” related entity field** ```json theme={null} { "OwnerName": [ "user/Owner", "user/name" ] } ``` **Field selection β€” sub-query (q/limit is required)** ```json theme={null} { "Assignees": { "q/from": "assignments/assignees", "q/select": { "Who": "user/name" }, "q/limit": 10 } } ``` **Field selection β€” aggregation** ```json theme={null} { "TotalBugs": [ "q/count", "fibery/id" ] } ``` Available aggregate functions: `q/count`, `q/sum`, `q/avg`, `q/min`, `q/max`. `q/sum`/`q/avg`/`q/min`/`q/max` require a numeric sub-field: `["q/sum", ["Related Field", "Number Field"]]`. **Filter operators by field type** | Field type | Operators | | -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Number, Date | `=`, `!=`, `<`, `<=`, `>`, `>=`, `q/null?` | | Text | `q/equals-ignoring-case?`, `q/not-equals-ignoring-case?`, `q/contains`, `q/not-contains`, `q/starts-with-ignoring-case?`, `q/ends-with-ignoring-case?`, `q/null-or-empty?` | | Boolean / null check | `["=", ["q/null?", ["FieldPath"]], "$boolParam"]` | | Reference | `q/in`, `q/not-in`, `q/count` | | Date-range start/end | `["q/start", ["FieldPath"]]`, `["q/end", ["FieldPath"]]` | | Location | `["q/address", ["FieldPath"]]` β€” parses to string, then use text operators | **Filter combinators** `q/and` and `q/or` take multiple filter clauses as operands: ```json theme={null} ["q/and", ["=", ["State"], "$state"], [">", ["Priority"], "$min"]] ``` **Constraint** β€” All filter values in `q/where` must use `$param` references. Inline literals will cause an error. **Constraint** β€” `q/limit` is required in every sub-query. **Example β€” bugs currently In Progress with assignees** ```json theme={null} { "query": { "q/from": "SoftDev/bug", "q/select": { "Name": [ "user/name" ], "Status": [ "workflow/state", "enum/name" ], "PublicId": [ "fibery/public-id" ], "Urgent": [ "user/urgent" ], "Assignees": { "q/from": "assignments/assignees", "q/select": { "Who": "user/name" }, "q/limit": 10 } }, "q/where": [ "q/equals-ignoring-case?", [ "workflow/state", "enum/name" ], "$status" ], "q/order-by": [ [ [ "fibery/creation-date" ], "q/desc" ] ], "q/limit": 20 }, "params": { "$status": "In Progress" } } ``` **Example output** ```json theme={null} [ { "Name": "Inline comment icons shift to a wrong position", "Status": "In Progress", "PublicId": "13959", "Urgent": false, "Assignees": [ { "Who": "Nastya Karabitskaya" } ] } ] ``` **Example β€” count all bugs** ```json theme={null} { "query": { "q/from": "SoftDev/bug", "q/select": [ "q/count", "fibery/id" ] } } ``` **Example β€” recent features with owner and planned dates** ```json theme={null} { "query": { "q/from": "Product Management/feature", "q/select": { "Name": [ "Product Management/name" ], "Status": [ "workflow/state", "enum/name" ], "Owner": [ "Product Management/owner", "user/name" ], "PlannedDates": [ "Product Management/planned-dates" ] }, "q/order-by": [ [ [ "fibery/creation-date" ], "q/desc" ] ], "q/limit": 5 } } ``` **Example output** ```json theme={null} [ { "Name": "Handle Large Datasets in Tables", "Status": "Icebox", "Owner": "Victor Zhuk", "PlannedDates": null }, { "Name": "Migrate on history v2 routes", "Status": "In Progress", "Owner": "Eugene Kisel", "PlannedDates": null } ] ``` ### `search` Searches workspace content using BM-25 keyword matching against entity titles, descriptions, document content, and comments. **Parameters** | Name | Type | Required | Description | | ---------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------- | | `query` | string | Yes | Search query string | | `database` | string | No | Limit results to a specific database | | `limit` | number | No | Max results. Default: `20`. Max: `100` | | `viewType` | string | No | Filter by view type: `document`, `grid`, `list`, `board`, `timeline`, `calendar`, `map`, `feed`, `gallery`, `gantt`, `form`, `report` | **Returns** β€” Matching items with `kind` (`"entity"` or `"view"`), `id`, `publicId`, `title`, `score`, `highlight`, `space`. **Note** β€” Highlights use `` tags around matched terms. **Example** ```json theme={null} { "query": "whiteboard", "limit": 3 } ``` **Example output** ```json theme={null} { "items": [ { "kind": "entity", "id": "78558418-4910-44a0-a19b-3fea99c0553d", "publicId": "3193", "title": "Whiteboards, Whiteboards, Whiteboards!", "score": 1256419.8, "highlight": { "kind": "title", "value": "Whiteboards, ..." }, "space": "Administrative" } ] } ``` ### `search_guide` Retrieves information from the Fibery User Guide via keyword search. **Parameters** | Name | Type | Required | Description | | ------- | ------ | -------- | ------------------------------- | | `query` | string | Yes | Question or topic to search for | **Returns** β€” Sorted list of relevant text fragments from the official Fibery User Guide. **Note** β€” Returns platform documentation only; does not search workspace content. ### `search_history` Searches the workspace activity history for entity and schema changes. **Parameters** | Name | Type | Description | | ---------------- | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | | `database` | string | Filter by database name | | `actions` | string\[] | Filter by action: `created`, `updated`, `deleted`, `collectionItemAdded`, `collectionItemRemoved`, `archived`, `restored`, `permissionsChanged` | | `schemaChange` | string\[] | Filter by schema change: `fieldChange`, `databaseChange`, `spaceChange` | | `entityId` | string (UUID) | Filter by `fibery/id` | | `entityPublicId` | string | Filter by public ID (requires `database`) | | `entityName` | string | Substring match on entity name. Minimum 3 characters | | `entityState` | string\[] | Filter by state: `EXIST`, `DELETED`, `ARCHIVED` | | `authorUserId` | string (UUID) | Filter by author's `fibery/id` | | `since` | string (ISO 8601) | Start of time range. Default: 24 hours ago | | `until` | string (ISO 8601) | End of time range. Default: now | | `limit` | number | Max items. Default: `50`. Max: `100` | | `sinceItem` | string | Cursor from previous result's `sinceItem` field, for pagination | **Returns** β€” History events. Each event includes `id`, `date`, `action`, `url`, `database`, `entity` (with `id`, `name`, `publicId`), `author`, `fromService`, and `changes` array (each change has `field`, `fieldTitle`, `to`). **Example β€” recent bugs created today** ```json theme={null} { "database": "SoftDev/bug", "actions": [ "created" ], "limit": 3 } ``` **Example output** ```json theme={null} { "items": [ { "id": "124888481", "date": "2026-03-18T15:12:18.183Z", "action": "created", "url": "https://the.fibery.io/SoftDev/bug/13961", "database": "SoftDev/bug", "databaseTitle": "Bug", "entity": { "id": "0000", "name": "Links and entities in the embedded view are not clickable", "publicId": "13961" }, "author": { "id": "d125f600-...", "name": "Alex Tsayun" }, "fromService": null, "changes": [ { "field": "user/name", "fieldTitle": "Name", "to": "Links and entities..." }, { "field": "user/urgent", "fieldTitle": "Urgent", "to": "false" } ] } ], "hasNext": true, "sinceItem": "124888481" } ``` **Example β€” paginating with sinceItem** ```json theme={null} { "database": "SoftDev/bug", "actions": [ "created" ], "sinceItem": "124888481", "limit": 50 } ``` ### `query_views` Returns saved views (boards, grids, timelines, documents, etc.) matching optional filters. **Parameters** | Name | Type | Description | | ------------ | ------------- | ----------------------------------------------------------------------------- | | `viewType` | string | Filter by view type | | `text` | string | Search in view name or description | | `id` | string (UUID) | Filter by `fibery/id` | | `publicId` | string | Filter by public ID | | `withConfig` | boolean | Include view config. Default: `true`. Set `false` when returning many results | **Returns** β€” View objects: type, name, description, space, config (if requested), and content for document views. ### `fetch_view_data` Executes a view's configured query and returns the entities it would display, with the view's own filters, fields, and sort order applied. **Parameters** | Name | Type | Required | Description | | ---------- | ------ | -------- | ------------------------------------------ | | `publicId` | string | Yes | Public ID of the view (from `query_views`) | | `limit` | number | No | Max entities to return. Default: `100` | | `offset` | number | No | Entities to skip. Default: `0` | **Prerequisite** β€” Call `query_views` first to find the view's `publicId`. **Note** β€” Unlike `query`, this executes the view's saved configuration rather than a custom query. ## Entities ### `create_entities` Creates one or more entities in a database. **Parameters** | Name | Type | Required | Description | | ---------- | --------- | -------- | ------------------------------------------------------------------------------------ | | `database` | string | Yes | Full database name | | `entities` | object\[] | Yes | Array of field-value maps. Keys in `"Space/FieldName"` format from `schema_detailed` | **Field value formats** | Field type | Value format | | ------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | | Text | `string` | | Number (int, decimal) | `number` | | Boolean | `boolean` | | Date | ISO date string: `"2026-04-01"` | | Date-range | `\{ "start": "YYYY-MM-DD", "end": "YYYY-MM-DD" \}` β€” end date is **exclusive** | | Single-select / single-relation | UUID string of the target entity | | Location | `\{ "longitude": number, "latitude": number, "fullAddress": string, "addressParts": \{ "place", "district", "region", "country" \} \}` | **Constraints** * `fibery/id` cannot be set; it is returned after creation. * `Collaboration~Documents/Document` fields: use `set_document_content`. * Collection fields: use `add_collection_items`. * `workflow/state`: use `set_state`. * Array values are never accepted; use `add_collection_items`. **Returns** β€” Created entity objects including their generated `fibery/id`. **Example β€” create a bug** ```json theme={null} { "database": "SoftDev/bug", "entities": [ { "user/name": "Form fields lose value on tab switch", "user/urgent": true, "SoftDev/Regression": false } ] } ``` **Example β€” create a feature with planned dates** ```json theme={null} { "database": "Product Management/feature", "entities": [ { "Product Management/name": "Bulk export to CSV", "Product Management/planned-dates": { "start": "2026-04-01", "end": "2026-04-15" } } ] } ``` ### `update_entities` Updates specified fields on existing entities. Only provided fields are changed; all other fields are left untouched. **Parameters** | Name | Type | Required | Description | | ---------- | --------- | -------- | ------------------------------------------------- | | `database` | string | Yes | Full database name | | `entities` | object\[] | Yes | Field-value maps. Each must include `"id"` (UUID) | **Constraints** β€” Same as `create_entities`: document fields, collections, and workflow state each have their own dedicated tools. **Example β€” mark a bug as urgent** ```json theme={null} { "database": "SoftDev/bug", "entities": [ { "id": "5766cc9a-ae80-4893-82d3-db8b78fdfa13", "user/urgent": true } ] } ``` ### `delete_entities` Permanently deletes entities by their UUIDs. This action cannot be undone. **Parameters** | Name | Type | Required | Description | | ---------- | ---------------- | -------- | --------------------------- | | `database` | string | Yes | Full database name | | `ids` | string\[] (UUID) | Yes | Array of `fibery/id` values | **Prerequisite** β€” Use `query` to find entity IDs before deleting. ### `set_state` Sets the `workflow/state` of a single entity. **Parameters** | Name | Type | Required | Description | | ---------- | ------------- | -------- | --------------------------------------------------------- | | `database` | string | Yes | Full database name | | `entityId` | string (UUID) | Yes | `fibery/id` of the entity | | `state` | string | Yes | State name, e.g. `"In Progress"`, `"Done"`, `"Won't Fix"` | **Note** β€” Only one workflow field exists per database. Available state names come from `schema_detailed` under the `workflow/state` field's `values:` section. **Example β€” move bug to Done** ```json theme={null} { "database": "SoftDev/bug", "entityId": "5766cc9a-ae80-4893-82d3-db8b78fdfa13", "state": "Done" } ``` **Available states for `SoftDev/bug`** (from live schema): `Icebox`, `Ready for Dev`, `In Progress`, `Implemented`, `In Testing`, `Tested`, `Done`, `Won't Fix` **Available states for `Product Management/feature`** (from live schema): `Icebox`, `Next`, `Ready for Dev`, `In Progress`, `Implemented`, `In Testing`, `Tested`, `Done`, `Abandoned` ### `get_entity_links` Generates Fibery web links for entities by their public IDs. **Parameters** | Name | Type | Required | Description | | ----------------- | --------- | -------- | ------------------------------------------------------- | | `database` | string | Yes | Full database name | | `entityPublicIds` | string\[] | Yes | Array of public ID strings (e.g., `["13961", "13960"]`) | **Note** β€” Public IDs are strings like `"13961"`, not UUIDs. Retrieve them via `query` by selecting `"fibery/public-id"`. **Example** ```json theme={null} { "database": "SoftDev/bug", "entityPublicIds": [ "13961", "13960" ] } ``` ## Documents ### `get_documents_content` Returns the Markdown content of one or more entity documents by their secrets. **Parameters** | Name | Type | Required | Description | | -------------- | --------- | -------- | ---------------------------------------------------------------------------------------------------------- | | `secrets` | string\[] | Yes | Document secrets | | `reducePrompt` | string | No | Summarization instruction for large documents. Default: `"Summarize this document in 2-3 paragraphs max."` | **How to get a document secret** β€” Query the entity and select the document field's secret path: ```json theme={null} { "query": { "q/from": "SoftDev/bug", "q/select": { "Name": [ "user/name" ], "DocSecret": [ "user/Description", "Collaboration~Documents/secret" ] }, "q/limit": 1 } } ``` ### `set_document_content` Replaces the full content of a `Collaboration~Documents/Document` field on an entity. **Parameters** | Name | Type | Required | Description | | ---------- | ------------- | -------- | ------------------------------------------------------------------------------------ | | `database` | string | Yes | Full database name | | `field` | string | Yes | Document field name (e.g., `"user/Description"`, `"Product Management/description"`) | | `entityId` | string (UUID) | Yes | `fibery/id` of the entity | | `content` | string | Yes | Full replacement content in Markdown. `""` clears the document | **Supported Markdown extensions** β€” Standard Markdown plus Fibery callouts: ```md theme={null} > [//]: # (callout;icon-type=icon;icon=info-circle;color=#199EE3) > Callout body here ``` **Example β€” set a bug description** ```json theme={null} { "database": "SoftDev/bug", "field": "user/Description", "entityId": "5766cc9a-ae80-4893-82d3-db8b78fdfa13", "content": "## Steps to Reproduce\n\n1. Open any embedded view\n2. Enter lock mode\n3. Click a link\n\n## Expected\nLink opens. **Actual:** Nothing happens." } ``` ### `append_document_content` Appends Markdown content to the end of an existing document field. Does not replace existing content. **Parameters** | Name | Type | Required | Description | | ---------- | ------------- | -------- | -------------------------- | | `database` | string | Yes | Full database name | | `field` | string | Yes | Document field name | | `entityId` | string (UUID) | Yes | `fibery/id` of the entity | | `content` | string | Yes | Markdown content to append | ## Collections ### `add_collection_items` Adds items to an entity's collection field (e.g., assignees, tags, linked bugs). **Parameters** | Name | Type | Required | Description | | ---------- | ---------------- | -------- | ---------------------------------------------------------------------- | | `database` | string | Yes | Full database name | | `field` | string | Yes | Collection field name (e.g., `"assignments/assignees"`, `"user/bugs"`) | | `entityId` | string (UUID) | Yes | `fibery/id` of the entity | | `items` | string\[] (UUID) | Yes | `fibery/id` values to add | **Example β€” assign a user to a bug** ```json theme={null} { "database": "SoftDev/bug", "field": "assignments/assignees", "entityId": "5766cc9a-ae80-4893-82d3-db8b78fdfa13", "items": [ "1d525780-5dcb-11e8-90b6-c6e140253257" ] } ``` ### `remove_collection_items` Removes items from an entity's collection field. **Parameters** | Name | Type | Required | Description | | ---------- | ---------------- | -------- | ---------------------------- | | `database` | string | Yes | Full database name | | `field` | string | Yes | Collection field name | | `entityId` | string (UUID) | Yes | `fibery/id` of the entity | | `items` | string\[] (UUID) | Yes | `fibery/id` values to remove | ## Spaces ### `create_space` Creates a new space in the workspace. **Parameters** | Name | Type | Required | Description | | ------------- | ------ | -------- | --------------------------------------------- | | `name` | string | Yes | Space name. Letters, numbers, and spaces only | | `description` | string | No | Space description | | `color` | string | No | Hex color code, e.g. `"#4CAF50"` | **Prerequisite** β€” Call `schema` to check for name conflicts. **Example** ```json theme={null} { "name": "Engineering", "description": "Engineering projects and tasks", "color": "#2978FB" } ``` ### `delete_space` Deletes a space and all its databases. Restorable via Activity Log. **Parameters** | Name | Type | Required | Description | | ------ | ------ | -------- | ---------------- | | `name` | string | Yes | Exact space name | **Constraint** β€” System spaces (`fibery/user`, `fibery/file`, `comments`, `highlights`, `vacations`) cannot be deleted. ## Databases ### `create_databases` Creates one or more databases within existing spaces. **Parameters** | Name | Type | Required | Description | | ----------- | --------- | -------- | ----------------------------- | | `databases` | object\[] | Yes | Array of database definitions | **Database definition** | Name | Type | Required | Description | | ------------- | ------ | -------- | --------------------------------------------------- | | `name` | string | Yes | `"Space/Database"` format. Space must already exist | | `description` | string | No | Database description | | `color` | string | No | Hex color code | **Auto-created fields** β€” `fibery/id`, `fibery/public-id`, system timestamps, `\{Space\}/Name`, `\{Space\}/Description`. **Constraint** β€” Names may contain only letters, numbers, and spaces. No `/`, `\`, `.`, `&`, `?`, `!`, etc. **Example** ```json theme={null} { "databases": [ { "name": "SoftDev/Sprint", "description": "Two-week development sprints", "color": "#673db6" } ] } ``` ### `rename_databases` Renames one or more databases. Changing the space prefix moves the database to a different space. **Parameters** | Name | Type | Required | Description | | ----------- | --------- | -------- | -------------------------- | | `databases` | object\[] | Yes | Array of rename operations | **Rename operation** | Name | Type | Required | Description | | --------- | ------ | -------- | -------------------------- | | `oldName` | string | Yes | Current full database name | | `newName` | string | Yes | New full database name | **Note** β€” Fibery automatically updates formula and relation references after rename. External API scripts using the old name will break. ### `delete_databases` Deletes one or more databases. Restorable via Activity Log. **Parameters** | Name | Type | Required | Description | | ----------- | --------- | -------- | ---------------------------- | | `databases` | string\[] | Yes | Array of full database names | ## Fields ### Naming restrictions (all field types) Field names may contain only letters, numbers, and spaces. Special characters (`/`, `\`, `.`, `&`, `,`, `?`, `!`, etc.) are not allowed in user-created names. ### `create_primitive_fields` Creates one or more primitive (scalar) fields in databases. **Parameters** | Name | Type | Required | Description | | -------- | --------- | -------- | -------------------------- | | `fields` | object\[] | Yes | Array of field definitions | **Field definition** | Name | Type | Required | Description | | ------------- | ------ | -------- | ----------------------------------------------------------- | | `database` | string | Yes | Full database name | | `name` | string | Yes | `"Space/FieldName"` β€” space must match the database's space | | `fieldType` | string | Yes | See types below | | `description` | string | No | Field description | | `meta` | object | No | Field-type-specific metadata | **Supported `fieldType` values** | Type | Description | Key `meta` options | | ---------------------------------- | ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `fibery/text` | Text | `ui/type`: `"text"`, `"email"`, `"url"`, `"phone"` | | `fibery/int` | Integer | β€” | | `fibery/decimal` | Decimal | `ui/number-format`: `"Number"`, `"Money"`, `"Percent"`; `ui/number-currency-code` (ISO 4217); `ui/number-precision`: 0–8; `ui/number-thousand-separator?`; `ui/number-unit` | | `fibery/bool` | Checkbox | β€” | | `fibery/date` | Date | β€” | | `fibery/date-time` | Date and time | β€” | | `fibery/date-range` | Date range | β€” | | `fibery/date-time-range` | Date and time range | β€” | | `fibery/location` | Location/address | β€” | | `Collaboration~Documents/Document` | Rich text document | β€” | **Example β€” add a URL field and a money field to bugs** ```json theme={null} { "fields": [ { "database": "SoftDev/bug", "name": "SoftDev/Reproduction Link", "fieldType": "fibery/text", "meta": { "ui/type": "url" } }, { "database": "SoftDev/bug", "name": "SoftDev/Fix Cost", "fieldType": "fibery/decimal", "meta": { "ui/number-format": "Money", "ui/number-currency-code": "USD", "ui/number-precision": 2 } } ] } ``` ### `rename_fields` Renames one or more fields. **Parameters** | Name | Type | Required | Description | | -------- | --------- | -------- | ----------------- | | `fields` | object\[] | Yes | Rename operations | **Rename operation** | Name | Type | Required | Description | | ---------- | ------ | -------- | --------------------------------------------------------- | | `database` | string | Yes | Full database name | | `oldName` | string | Yes | Current field name | | `newName` | string | Yes | New field name (same `"Space/"` prefix, different suffix) | ### `delete_fields` Deletes one or more fields. Restorable via Activity Log. **Parameters** | Name | Type | Required | Description | | -------- | --------- | -------- | ---------------- | | `fields` | object\[] | Yes | Field references | **Field reference** | Name | Type | Required | Description | | ---------- | ------ | -------- | ------------------------------------ | | `database` | string | Yes | Full database name | | `field` | string | Yes | Field name in `"Space/Field"` format | **Note** β€” Deleting a relation field also removes its counterpart in the related database. ### `create_relation_fields` Creates a relation between two databases. One relation definition creates a field in both databases. **Parameters** | Name | Type | Required | Description | | -------- | --------- | -------- | -------------------- | | `fields` | object\[] | Yes | Relation definitions | **Relation definition** | Name | Type | Required | Description | | ------------------- | ------ | -------- | ----------------------------------------------------------------------------------------- | | `database` | string | Yes | Source database | | `relationDatabase` | string | Yes | Target database | | `name` | string | Yes | Field name in source database | | `relationFieldName` | string | Yes | Field name in target database. Use `"user/FieldName"` prefix when target is `fibery/user` | | `cardinality` | string | Yes | `"one-to-one"`, `"one-to-many"`, `"many-to-one"`, `"many-to-many"` | | `description` | string | No | Field description | **Example β€” link bugs to customer requests** ```json theme={null} { "fields": [ { "database": "SoftDev/bug", "relationDatabase": "Customer Success/Customer Request", "name": "SoftDev/Customer Requests", "relationFieldName": "Customer Success/Related Bugs", "cardinality": "many-to-many" } ] } ``` ### `create_single_select_fields` Creates one or more single-select fields with predefined options. **Parameters** | Name | Type | Required | Description | | -------- | --------- | -------- | ----------------- | | `fields` | object\[] | Yes | Field definitions | **Field definition** | Name | Type | Required | Description | | --------------------------- | --------- | -------- | ------------------------------------------------------------------------------------ | | `database` | string | Yes | Full database name | | `name` | string | Yes | Field name | | `options` | object\[] | Yes | Options: `\{ "name": string, "color"?: string, "icon"?: string, "value"?: number \}` | | `defaultOption` | string | No | Option name to use as default for new entities | | `allowNumberValueForOption` | boolean | No | Enable numeric values per option. Default: `false` | **Example β€” add severity field to bugs** ```json theme={null} { "fields": [ { "database": "SoftDev/bug", "name": "SoftDev/Severity", "options": [ { "name": "Critical", "color": "#d40915" }, { "name": "High", "color": "#fc551f" }, { "name": "Medium", "color": "#fba32f" }, { "name": "Low", "color": "#99a2ab" } ], "defaultOption": "Medium" } ] } ``` ### `update_single_select_fields` Updates options of existing single-select fields. **Parameters** | Name | Type | Required | Description | | -------- | --------- | -------- | ----------------- | | `fields` | object\[] | Yes | Update operations | **Update operation** | Name | Type | Required | Description | | --------------- | -------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------- | | `database` | string | Yes | Full database name | | `name` | string | Yes | Field name | | `update` | object | Yes | Full replacement: `\{ "options": [...] \}`. Incremental: `\{ "addOptions": [...], "updateOptions": [...], "removeOptions": ["name"] \}` | | `defaultOption` | string \| null | No | New default option. `null` clears the default | **Note** β€” Incremental operations execute in order: remove β†’ update β†’ add. `updateOptions` matches by name; it can change `color`, `icon`, or numeric `value`, but not the option name itself. ### `create_multi_select_fields` Creates one or more multi-select fields. Same parameters as `create_single_select_fields`, without `defaultOption`. ### `update_multi_select_fields` Updates options of existing multi-select fields. Same parameters as `update_single_select_fields`, without `defaultOption`. ### `create_workflow_field` Creates a workflow (state) field for tracking entities through lifecycle stages. **Parameters** | Name | Type | Required | Description | | --------------- | --------- | -------- | -------------------------------------------------------------------------------------------------- | | `database` | string | Yes | Full database name | | `options` | object\[] | Yes | States: `\{ "name": string, "type": "Not started" \| "Started" \| "Finished", "color"?: string \}` | | `defaultOption` | string | Yes | State name for new entities | **Constraint** β€” Only one workflow field per database. Always addressed as `"workflow/state"` in queries. **Example β€” add workflow to a new database** ```json theme={null} { "database": "SoftDev/Sprint", "options": [ { "name": "Planning", "type": "Not started", "color": "#99a2ab" }, { "name": "Active", "type": "Started", "color": "#8ec351" }, { "name": "Review", "type": "Started", "color": "#fba32f" }, { "name": "Done", "type": "Finished", "color": "#4a4a4a" } ], "defaultOption": "Planning" } ``` ### `update_workflow_field` Updates the states of an existing workflow field. **Parameters** | Name | Type | Required | Description | | --------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------ | | `database` | string | Yes | Full database name | | `update` | object | Yes | Full replacement: `\{ "options": [...] \}`. Incremental: `\{ "addOptions": [...], "updateOptions": [...], "removeOptions": [...] \}` | | `defaultOption` | string | No | New default state. Unchanged if not provided | **Constraint** β€” The current default state cannot be removed until a new default is set. ### `delete_workflow_field` Deletes the workflow field from a database. Restorable via Activity Log. **Parameters** | Name | Type | Required | Description | | ---------- | ------ | -------- | ------------------ | | `database` | string | Yes | Full database name | ### `create_formula_field` Creates a calculated formula field. The formula expression is generated automatically from a natural-language description. **Parameters** | Name | Type | Required | Description | | ------------- | ------ | -------- | ------------------------------------------------- | | `database` | string | Yes | Full database name | | `name` | string | Yes | Field name in `"Space/Field"` format | | `description` | string | Yes | Natural-language description of what to calculate | **Note** β€” Formula fields are always read-only. The generated formula appears in `schema_detailed` under the `formula:` comment. **Example β€” count open bugs per feature** ```json theme={null} { "database": "Product Management/feature", "name": "Product Management/Open Bug Count", "description": "Count of linked bugs whose state is not Done, Tested, or Won't Fix" } ``` ### `update_formula_field` Updates an existing formula field by regenerating its expression. The new formula must produce a compatible type with the existing field. **Parameters** | Name | Type | Required | Description | | ------------- | ------ | -------- | ---------------------------------- | | `database` | string | Yes | Full database name | | `name` | string | Yes | Name of the existing formula field | | `description` | string | Yes | New description for the formula | ### `create_files_fields` Creates file attachment fields. **Parameters** | Name | Type | Required | Description | | -------- | --------- | -------- | ----------------- | | `fields` | object\[] | Yes | Field definitions | **Field definition** | Name | Type | Required | Description | | -------------------- | ------- | -------- | -------------------------- | | `database` | string | Yes | Full database name | | `name` | string | Yes | Field name | | `allowMultipleFiles` | boolean | Yes | Allow multiple attachments | | `description` | string | No | Field description | ### `create_avatars_fields` Enables avatar/profile picture attachments on entities. Creates an `"avatar/avatars"` field automatically. **Parameters** | Name | Type | Required | Description | | ----------- | --------- | -------- | ------------------- | | `databases` | string\[] | Yes | Full database names | ### `delete_avatars_fields` Removes the `"avatar/avatars"` field. Restorable via Activity Log. **Parameters** | Name | Type | Required | Description | | ----------- | --------- | -------- | ------------------- | | `databases` | string\[] | Yes | Full database names | ### `create_comments_fields` Enables comments on entities. Creates a `"comments/comments"` field automatically. **Parameters** | Name | Type | Required | Description | | ----------- | --------- | -------- | ------------------- | | `databases` | string\[] | Yes | Full database names | ### `delete_comments_fields` Removes the `"comments/comments"` field. Restorable via Activity Log. **Parameters** | Name | Type | Required | Description | | ----------- | --------- | -------- | ------------------- | | `databases` | string\[] | Yes | Full database names | ### `create_icon_fields` Enables emoji icons on entities. Creates an `"icon/icon"` field automatically. **Parameters** | Name | Type | Required | Description | | ----------- | --------- | -------- | ------------------- | | `databases` | string\[] | Yes | Full database names | ### `delete_icon_fields` Removes the `"icon/icon"` field. Restorable via Activity Log. **Parameters** | Name | Type | Required | Description | | ----------- | --------- | -------- | ------------------- | | `databases` | string\[] | Yes | Full database names | ## Views ### `create_view` Creates a new view in the workspace. **Parameters** | Name | Type | Required | Description | | ------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------- | | `viewType` | string | Yes | See view types below | | `name` | string | Yes | View name | | `config` | object | No | View configuration (structure varies by type) | | `content` | string | No | Markdown content (document views only) | | `description` | string | No | Short Markdown description | | `space` | string | No | Space to create the view in. Defaults to inferred from databases in config. Use `"Private"` for private space | **View types** | Type | Description | | ---------- | ------------------------------------------------------ | | `grid` | Spreadsheet-like table, supports hierarchical grouping | | `list` | Simple linear list | | `board` | Kanban board, grouped by relation or enum field | | `timeline` | Horizontal time-bar view | | `calendar` | Date-based calendar | | `map` | Geographic map (requires location fields) | | `feed` | Rich-text document feed | | `gallery` | Card gallery with optional cover images | | `gantt` | Hierarchical grid + timeline | | `form` | Data-entry form for creating entities | | `document` | Standalone rich-text document | **`FieldUnit` format** (used in `fields` arrays inside config) ```json theme={null} { "field": "Space/FieldName", "showCount": true } ``` `"db-badge"` and `"db-badge-abbr"` are also valid `field` values. **FilterNode `nodeType` values and their operators** | nodeType | Operators | | --------------- | ---------------------------------------------------------------------------------------------------------------------------------- | | `text` | `contains`, `does-not-contain`, `is`, `is-not`, `starts-with`, `ends-with`, `is-empty`, `is-not-empty` | | `number` | `equals`, `does-not-equal`, `greater-than`, `greater-than-or-equal`, `less-than`, `less-than-or-equal`, `is-empty`, `is-not-empty` | | `bool` | `is` | | `date` | `is`, `is-before`, `is-after`, `is-on-or-before`, `is-on-or-after`, `is-empty`, `is-not-empty` | | `reference` | `is-empty`, `is-not-empty` | | `collection` | `is-empty`, `is-not-empty`, `contains-me`, `does-not-contain-me` | | `single-select` | `is`, `is-not`, `is-any-of`, `is-none-of`, `is-empty`, `is-not-empty` | | `workflow` | `is`, `is-not`, `is-any-of`, `is-none-of`, `is-empty`, `is-not-empty` | | `multi-select` | `is-empty`, `is-not-empty`, `contains-any-of`, `contains-none-of`, `contains`, `does-not-contain` | | `logical` | Combines filters with `and` / `or` | For `single-select` and `workflow` filter values, pass arrays of option/state UUIDs. For date-range field paths, use `["q/start", "Space/Dates"]`. **Config structure by view type** * **grid / list** β€” `items[]` (ItemConfig with optional `groupBy`), `rowHeight` (`"short"`, `"medium"`, `"tall"`, `"extra-tall"`), `hideEmptyParentGroups` * **board** β€” `x[]` (AxisConfig, required), `y[]` (optional), `items[]`, `cardSize` (`"compact"`, `"comfortable"`, `"spacious"`) * **timeline / gantt** β€” `items[]` with `startDate`, `endDate`, `dependencyField`; `milestones[]`; `dependencyDateShiftingMode` (`"none"`, `"consume-gap"`, `"preserve-gap"`) * **calendar** β€” `items[]` with `startDate`, `endDate` * **map** β€” `items[]` with `location`; `style` (`"default"`, `"muted"`, `"satellite"`) * **feed** β€” `items[]` with `post` (document field); `postWidth` (`"narrow"`, `"medium"`, `"full"`) * **gallery** β€” `items[]` with `cover` (file field), `fillCover`; `cardSize` (`"compact"`, `"medium"`, `"full"`) * **form** β€” `database`, `fields[]` with `field`, `displayName`, `required`, `description`, `hidden`, `defaultValue` * **document** β€” No config needed; use `content` parameter **AxisConfig** (board, timeline, gallery) β€” extends ItemConfig with `forDatabase`, `field` (relation or enum field only), `hideEmptyLanes`. ### `update_view` Updates an existing view. Only provided fields are changed. **Parameters** | Name | Type | Required | Description | | ------------- | ------------- | -------- | ----------------------------------------------------------------------- | | `viewType` | string | Yes | View type (required for routing) | | `id` | string (UUID) | Yes | `fibery/id` of the view (from `query_views`) | | `name` | string | No | New name | | `description` | string | No | New description | | `config` | object | No | Updated configuration | | `content` | string | No | New Markdown content (document views only) | | `append` | boolean | No | If `true`, appends `content` instead of replacing (document views only) | | `space` | string | No | Move the view to a different space | ### `delete_views` Deletes views by UUID. Entity data is not affected. Restorable via Activity Log. **Parameters** | Name | Type | Required | Description | | ----- | ---------------- | -------- | ----------------------- | | `ids` | string\[] (UUID) | Yes | View `fibery/id` values | ## Files & Import ### `add_file_from_url` Downloads a file from a URL and attaches it to an entity's file field. **Parameters** | Name | Type | Required | Description | | ---------- | ------------- | -------- | --------------------------------------- | | `database` | string | Yes | Full database name | | `field` | string | Yes | File field name (e.g., `"Files/Files"`) | | `entityId` | string (UUID) | Yes | `fibery/id` of the entity | | `url` | string (URI) | Yes | HTTPS URL to download from | | `fileName` | string | Yes | Name to assign the attached file | **Example** ```json theme={null} { "database": "SoftDev/bug", "field": "Files/Files", "entityId": "5766cc9a-ae80-4893-82d3-db8b78fdfa13", "url": "https://example.com/screenshots/bug-13961.png", "fileName": "bug-13961-screenshot.png" } ``` ### `get_connectors_list` Returns all available built-in connectors (import integrations) in the workspace. **Parameters** β€” None. **Returns** β€” Connectors with `name`, `id`, and supported modes (one-time import and/or continuous sync). **Note** β€” When the desired source is not listed, use the `csv` connector as a fallback. ### `get_import_link` Generates a URL to the Fibery import wizard for a given connector and target. **Parameters** | Name | Type | Required | Description | | ------------- | ------- | -------- | -------------------------------------------------------------------- | | `spaceName` | string | Yes | Target space name | | `isSync` | boolean | Yes | `true` for continuous sync; `false` for one-time import | | `connectorId` | string | Yes | Connector ID from `get_connectors_list` | | `dbName` | string | No | Existing database name to import into. Omit to create a new database | **Prerequisite** β€” Call `get_connectors_list` first to obtain valid `connectorId` values. ## Tool Quick Reference | Tool | Category | Description | | ----------------------------- | ----------- | ---------------------------------------------------------------------- | | `get_me` | Workspace | Current user info | | `schema` | Workspace | All spaces and databases | | `schema_detailed` | Workspace | Fields, types, enums for specific databases | | `query` | Querying | Flexible entity query with filtering, sorting, pagination, sub-queries | | `search` | Querying | BM-25 keyword search across workspace content | | `search_guide` | Querying | Search the Fibery User Guide documentation | | `search_history` | Querying | Activity log: creates, updates, deletes, schema changes | | `query_views` | Querying | Find saved views by type or name | | `fetch_view_data` | Querying | Execute a saved view's query and return its entities | | `create_entities` | Entities | Create new entities | | `update_entities` | Entities | Update entity fields | | `delete_entities` | Entities | Permanently delete entities | | `set_state` | Entities | Set workflow/state | | `get_entity_links` | Entities | Generate web links by public ID | | `get_documents_content` | Documents | Read document field content as Markdown | | `set_document_content` | Documents | Replace full document field content | | `append_document_content` | Documents | Append to document field content | | `add_collection_items` | Collections | Add items to a collection field | | `remove_collection_items` | Collections | Remove items from a collection field | | `create_space` | Spaces | Create a new space | | `delete_space` | Spaces | Delete a space and all its databases | | `create_databases` | Databases | Create new databases in a space | | `rename_databases` | Databases | Rename or move databases between spaces | | `delete_databases` | Databases | Delete databases | | `create_primitive_fields` | Fields | Create scalar fields (text, number, date, bool, etc.) | | `rename_fields` | Fields | Rename fields | | `delete_fields` | Fields | Delete fields | | `create_relation_fields` | Fields | Create relations between databases | | `create_single_select_fields` | Fields | Create single-select enum fields | | `update_single_select_fields` | Fields | Add, update, or remove single-select options | | `create_multi_select_fields` | Fields | Create multi-select enum fields | | `update_multi_select_fields` | Fields | Add, update, or remove multi-select options | | `create_workflow_field` | Fields | Create a workflow/state field | | `update_workflow_field` | Fields | Add, update, or remove workflow states | | `delete_workflow_field` | Fields | Delete the workflow field | | `create_formula_field` | Fields | Create a calculated formula field from a description | | `update_formula_field` | Fields | Update a formula field's expression | | `create_files_fields` | Fields | Create file attachment fields | | `create_avatars_fields` | Fields | Enable avatar attachments on entities | | `delete_avatars_fields` | Fields | Disable avatar attachments | | `create_comments_fields` | Fields | Enable comments on entities | | `delete_comments_fields` | Fields | Disable comments | | `create_icon_fields` | Fields | Enable emoji icons on entities | | `delete_icon_fields` | Fields | Disable emoji icons | | `create_view` | Views | Create a new view | | `update_view` | Views | Update an existing view | | `delete_views` | Views | Delete views | | `add_file_from_url` | Files | Download and attach a file to an entity | | `get_connectors_list` | Import | List available import connectors | | `get_import_link` | Import | Generate an import wizard URL | # Response envelope Source: https://developers.fibery.com/api-reference/response-envelope Understand the response shape returned by the Fibery HTTP API. Every call to `/api/commands` returns a JSON envelope with two keys: `success` (boolean) and `result` (the command's payload on success, the error on failure). ```json theme={null} {"success": true, "result": [{"fibery/id": "7dcf4730-82d2-11e9-8a28-82a9c787ee9d", "user/name": "Arthur Dent"}]} ``` ## Errors When `success` is `false`, `result` holds the error: | Key | Description | | --------- | ------------------------------------------------------------------------- | | `name` | Stable error code, safe to switch on. | | `message` | Human-readable explanation. | | `data` | Error-specific context (e.g. the query, offending field, schema version). | ```json theme={null} { "success": false, "result": { "name": "entity.error/schema-type-not-found", "message": "\"Cricket/Nope\" database was not found.", "data": { "error/schema-version": 15738, "type": "Cricket/Nope", "query": {"q/from": "Cricket/Nope", "q/select": ["fibery/id"], "q/limit": 1}, "params": {}, "param-types": {}, "param-collections": null } } } ``` ## Transport errors Returned without the `success`/`result` envelope β€” the command never reached the dispatcher. | HTTP | When | Body | | ----- | ------------------------------------------------------------------------------------------------- | ------------------------------------------------ | | `401` | Missing or invalid API token | Plain text `Unauthorized` | | `429` | Rate limit exceeded (see [Request limits](/guides/getting-started/authentication#request-limits)) | Plain text | | `500` | Unknown command name, malformed JSON body, or unexpected server error | `{"name": "...", "message": "...", "data": ...}` | # Terminology Source: https://developers.fibery.com/guides/general/terminology Core Fibery concepts and the UI/API naming differences. | Term | Definition | | ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Space** | A hub with data and Views relevant to a specific process. For example: Software Development, Candidates tracking, Vacations tracking, CRM, HR, etc. | | **Database** | A collection of data records (Entities) that share the same characteristics (Fields). | | **Entity** | A Database record. For example, you may have an Interviews Database, and every record in this Database is an Entity. | | **Field** | A way to store information of a certain kind for all Database Entities, such as text, number, relation, etc. You can add as many Fields as you want for every Database. | | **Schema** | The metadata describing every Database and its Fields. | | **View** | A way to visualize and edit data records from one or several Databases in order to analyze them and make decisions. | ## API naming To avoid confusion, please note that the terms in the API may differ from those in the Fibery User Guide and the interface. * **Type** β†’ Database * **App** β†’ Space Initially, we used Type and App across all assets and interface. Based on user feedback, we decided to rename them as Database and Space. However, the old terms still exist in the API code and are likely to remain unchanged. # Authentication Source: https://developers.fibery.com/guides/getting-started/authentication Authenticate HTTP API requests using token-based authorization or OAuth. Fibery API uses token-based authentication. That means you need to pass your API token with every request. This token should be the same for all requests, there is no need to generate a new one each time. Your API token carries the same privileges as your user, so be sure to keep it secret. ```javascript JavaScript theme={null} const response = await fetch('https://YOUR_ACCOUNT.fibery.io/api/commands', { method: 'POST', headers: { 'Authorization': 'Token YOUR_TOKEN', 'Content-Type': 'application/json' }, body: JSON.stringify({ command: 'fibery.entity/query', args: { query: { 'q/from': 'fibery/user', 'q/select': ['fibery/id', 'user/name'], 'q/where': ['=', ['fibery/id'], '$my-id'], 'q/limit': 1 } } }) }); const data = await response.json(); ``` ```bash cURL theme={null} curl -X POST https://YOUR_ACCOUNT.fibery.io/api/commands \ -H 'Authorization: Token YOUR_TOKEN' \ -H 'Content-Type: application/json' \ -d '{ "command": "fibery.entity/query", "args": { "query": { "q/from": "fibery/user", "q/select": ["fibery/id", "user/name"], "q/where": ["=", ["fibery/id"], "$my-id"], "q/limit": 1 } } }' ``` Make sure to replace your account name and token with the actual values. ## Managing tokens The number of tokens is limited to **3 per user**. You can generate, list and delete tokens on the "API Tokens" page available from the workspace menu. image.png You can also manage the tokens directly using the API. The following endpoints are available to manage access tokens: * `GET /api/tokens` β€” lists all access tokens that were given to current user * `POST /api/tokens` β€” creates new token for current user * `DELETE /api/tokens/:token_id` β€” deletes token by id You need to be authenticated with a browser cookie or with an already existing token when accessing these endpoints. ## Request limits To ensure system stability and consistent user experience, our API is rate-limited. Rate-limited requests will return a "Too Many Requests" error (HTTP response status `429`). The rate limit for incoming requests is **3 requests per second per token**. Additionally the entire workspace is limited to 7 requests per second. Rate limits may change. In the future we may adjust rate limits to balance for demand and reliability. ## OAuth flow If your app acts on behalf of Fibery users, use OAuth 2.0 instead of a static API token. Once the flow completes, you get an access token that is used exactly like an API token β€” pass it as `Authorization: Bearer ` to every Fibery API request. Note the scheme difference: OAuth access tokens use the `Bearer` prefix, while static API tokens use the `Token` prefix. OAuth apps are not self-service. Contact Fibery Support to register your app. We'll create the client and share the `client_id`, `client_secret`, and whitelist your `redirect_uri`. ### Endpoints Fibery uses the standard Authorization Code grant. Two endpoints live under `https://auth.fibery.io`: | Purpose | Endpoint | | ------------------------ | ------------------------------------------ | | Authorization | `GET https://auth.fibery.io/oauth2/auth` | | Token exchange & refresh | `POST https://auth.fibery.io/oauth2/token` | ### Scopes | Scope | What it does | | --------- | ------------------------------------------------------------------------------------------------------------- | | `openid` | Returns an ID token with the authenticated user's identity. | | `offline` | Returns a `refresh_token` alongside the access token so you can stay connected without re-prompting the user. | Request both scopes unless you have a reason not to. ### Flow Refer to [RFC 6749](https://datatracker.ietf.org/doc/html/rfc6749) for the spec details of each step β€” Fibery follows it as-is. # FAQ Source: https://developers.fibery.com/guides/graphql/faq Frequently asked questions about the Fibery GraphQL API. If this page doesn't answer your question, please contact us in the support chat. ## Is there an `or` operator available in GraphQL? Indeed there is no `or` operation. We propose use several queries in this case. ```graphql theme={null} { nullName: findProjects(name: {isNull: true}) { id } emptyName: findProjects(name: {is: ""}) { id } } ``` ## Can a GraphQL "find" query return *only* the count of records found? Unfortunately, it is not supported in GraphQL for now. However many things can be done using native [Fibery API](/guides/http-api/query-entities#select-fields). ## Can you provide a GraphQL example that adds multiple selections to a multi-select field? ```graphql theme={null} mutation { articles(id: {isNull: false}) { linkTags(name: {in: ["One", "Two"]}) {message} } } ``` Example above for databases, and for enums you can just use update ```graphql theme={null} mutation { articles(id: {isNull: false}) { update(multiSelect: {name: {in: ["One", "Two"]}}) {message} } } ``` ## Does the Fibery GraphQL API support fragments? No, fragments are not supported in the current version of the Fibery GraphQL API.\ We recommend using fully expanded queries instead. * Why aren't fragments supported? Fragments are a powerful way to make queries more reusable and maintainable. However, Fibery's GraphQL implementation prioritizes simplicity and stability, and fragment support is currently not on our roadmap. * What should I do instead? If you're using fragments to avoid repeating fields across queries, you'll need to manually repeat those fields in each query. While it may add a bit of duplication, this approach is fully compatible with the current API. * Will fragment support be added in the future? At this point, we don't plan to support fragments. If your use case critically depends on them, feel free to share more details with us β€” we're always open to learning how we can improve. # GraphQL mutations Source: https://developers.fibery.com/guides/graphql/mutations Learn how to create and update Entities using the Fibery GraphQL API. # Mutations Mutations should be used to modify database content. We implemented a set of operations based on automation's actions. These operations can be performed one by one for created or filtered database's entities. In other words multiple actions can be executed for added entities or selected by filtering arguments. Find below the syntax of mutation. Filter argument to select entities for modification is the same as defined for find query. Every action has the result. It is a `message` about action execution, affected `entities`. ```graphql theme={null} mutation { database(filter) { action1(args) {message, entities {id, type}} action2(args) {message, entities {id, type}} ... actionN(args) {message, entities {id, type}} } } ``` The operations can not be duplicated inside mutation. Batch alternative of action can be used in case multiple arguments supposed to be performed for the same action. Batch action `data` argument is an array of actions' arguments. ```graphql theme={null} # Single command action(arguments) {message} # Batch command actionBatch(data: [arguments]) {message} ``` The available database actions or operations can be found in Docs β†’ Mutation. g-mutations-49d53329.gif Example of creating entity and appending content to its rich field ```graphql theme={null} mutation { bugs { create(name: "New Bug") {message} appendContentToStepsToReproduce(value: "TBD") {message} } } ``` Output: ```json theme={null} { "data": { "bugs": { "create": { "message": "Create: 1 Bug added" }, "appendContentToStepsToReproduce": { "message": "Append content to Steps To Reproduce: Steps To Reproduce updated for 1 Bug" } } } } ``` Example of closing bugs with name "New Bug" and notifying assignees using text template ```graphql theme={null} mutation { bugs(name: {is: "New Bug"}) { moveToFinalState {message} notifyAssignees(subject: "{{Name}} bug was closed") {message} } } ``` Output ```json theme={null} { "data": { "bugs": { "moveToFinalState": { "message": "Move to final state executed" }, "notifyAssignees": { "message": "Notify Assignees: 0 notifications sent" } } } } ``` ## **Create** New entities can be added to database using `create` or `createBatch`. The arguments of these actions native database fields. One-to-one fields and inner list items also can be linked. Create one release and link all bugs in "To Do" state ```graphql theme={null} mutation { releases { create( name: "Urgent" bugs: { state: {name: {is: "To Do"}} } ) { entities { id type } } } } ``` Output ```json theme={null} { "data": { "releases": { "create": { "entities": [ { "id": "13114b4a-fa4b-400a-8da3-257cef0e22f5", "type": "Software Development/Release" } ] } } } } ``` Create several bugs in different states, assign to current user and add comment ```graphql theme={null} mutation { bugs { createBatch(data: [ {name: "Login failure", state: {name: {is: "In Progress"}}} {name: "Design is br0ken", state: {name: {is: "To Do"}}} ]) {message} assignToMe {message} addComment(value: "I will fix this bug ASAP") {message} } } ``` Output ```json theme={null} { "data": { "bugs": { "createBatch": { "message": "Create: 2 Bugs added" }, "assignToMe": { "message": "Assign to me: User(s) assigned to 2 Bugs" }, "addComment": { "message": "Add comment: Comments added to 2 Bugs" } } } } ``` ## **Update** Use update action if it is required to modify some fields or relations. The database mutation filter argument for selection entities should be provided for actions like `update`. Change effort and release of bugs in "To Do" with effort equals to 15 ```graphql theme={null} mutation { bugs(effort: {is: 15}, state: {name: {is: "To Do"}}) { update( release: {name: {is: "1.0"}} effort: 10 ) {entities {id}} countOfEntities # count of found bugs } } ``` Output ```json theme={null} { "data": { "bugs": { "update": { "entities": [ { "id": "fa39df10-912b-11eb-a0bf-cb515797cdf8" }, { "id": "f42d7db6-6c6f-429d-bf5c-c60b0d9d072d" }, { "id": "fb670021-4fe6-4489-86b4-8a17e60e9227" } ] }, "countOfEntities": 3 } } } ``` ## Delete Use `delete` action to remove entities which satisfy provided criterion. Be careful with this command and verify that only required entities are going to be deleted by using find command before execute `delete`. Verify entities to be deleted using listEntities or countOfEntities ```graphql theme={null} mutation { bugs(state: {name: {is: "Done"}}) { listEntities {id} countOfEntities } } ``` Proceed with delete ```graphql theme={null} mutation { bugs(state: {name: {is: "Done"}}) { delete {message} } } ``` Output ```json theme={null} { "data": { "bugs": { "delete": { "message": "Delete: 4 Bugs deleted" } } } } ``` ## Create and link relations Related one-to-one, one-to-many or many-to-many entities can be created using `AddRelation` or `AddRelationItem` actions. Mind using GraphQL aliases to have convenient names in output ```graphql theme={null} mutation { releases { release: create(name: "3.0.1") {entities {id, type}} tasks: addTasksItemBatch(data: [ {name: "Do design"} {name: "Do development"} ]) {entities {id, type}} bugs: addBugsItemBatch(data: [ {name: "Fix design"} {name: "Fix development"} {name: "Remove code"} ]) {entities {id, type}} } } ``` Output ```json theme={null} { "data": { "releases": { "release": { "entities": [ { "id": "c09b246b-3e47-49fa-9a28-94438dc640c3", "type": "Software Development/Release" } ] }, "tasks": { "entities": [ { "id": "e9ddd110-e8ac-11ec-a77e-d74e2f66036c", "type": "Software Development/Task" }, { "id": "e9f095c0-e8ac-11ec-a77e-d74e2f66036c", "type": "Software Development/Task" } ] }, "bugs": { "entities": [ { "id": "ea19a190-e8ac-11ec-a77e-d74e2f66036c", "type": "Software Development/bug" }, { "id": "ea290ae0-e8ac-11ec-a77e-d74e2f66036c", "type": "Software Development/bug" }, { "id": "ea3a48f0-e8ac-11ec-a77e-d74e2f66036c", "type": "Software Development/bug" } ] } } } } ``` ## Rich Text fields There are some additional actions which may be handy in modifying rich fields and generating PDFs. Rich fields can be modified by actions started from `appendContent`, `overwriteContent`, `prependContent`. `attachPdfUsingTemplate` can be used for generating PDF files. The templating capabilities can be used in the same way it is available in automations. Find more information about markdown templates in [Markdown Templates](https://the.fibery.io/@public/User_Guide/Guide/Markdown-Templates-53). ```graphql theme={null} mutation { bugs(assignees: {containsAny: {email: {contains: "oleg"}}}) { appendContentToStepsToReproduce(value: "Line for state: {{State.Name}}") {message} attachPdfUsingTemplate(value: "{{CurrentUser.Email}} is an author") {message} } } ``` Here is the result of the mutations: image.png ## Unlink/Link relations Relations can be linked or unlinked using `link` or `unlink` actions. ```graphql theme={null} mutation { bugs(release: {id: {isNull: false}}) { unlinkRelease {message} } } ``` Output ```json theme={null} { "data": { "bugs": { "unlinkRelease": { "message": "Unlink Release: Release unlinked from 12 Bugs" } } } } ``` Link bugs with effort greater than 5 to empty release ```graphql theme={null} mutation { releases(name: {is: "3.0"}) { linkBugs(effort: {greater: 5}) {message} } } ``` Unlink bugs with effort greater than 15 from release ```graphql theme={null} mutation { releases(bugs: {isEmpty: false}) { unlinkBugs(effort: {greaterOrEquals: 15}) {message} } } ``` ## Delete relations Relations can be removed from system by executing `delete`. Again be careful with this operation since the data will be erased. Delete release from bugs with release ```graphql theme={null} mutation { bugs(release: {id: {isNull: false}}) { deleteRelease {message} } } ``` Delete bug items in "Done" state from selected release ```graphql theme={null} mutation { releases(bugs: {isEmpty: false}) { deleteBugs(state: {name: {is: "Done"}}) {message} } } ``` ## Send notifications via GraphQL The build-in notifications can be sent using `NotifyUsers` and other actions related to notifications like it is done for automatons. Text templates are supported. Text templates have the same capabilities as Markdown Templates **excluding support of Markdown Syntax**, in other words it is just a text. ```graphql theme={null} mutation { bugs(release: {id: {isNull: false}}) { notifyCreatedBy(subject: "Please take a look") {message} notifyAssignees( subject: "Fix ASAP" message: "Fix bug {{Name}} ASAP" ) {message} notifyUsers( to: {email: {contains: "oleg"}} message: "Waiting for fix..." ) {message} } } ``` ## Add file from URL The file can be attached to entities by using `addFileFromUrl` action. ```graphql theme={null} mutation { bugs(release: {id: {isNull: false}}) { addFileFromUrl( name: "Super File" url: "https://fibery.com/img/fibery-logo.svg" ) {message} } } ``` # How to avoid timeouts Request (server) timeouts can be a problem for long-running tasks. We propose several ways to avoid it: 1. Run mutations as background job 2. Use paging for mutations to reduce amount of data to be processed. ## **Use background jobs** There is a possibility in our GraphQL API to execute long-running actions in background. Huge set of operations with large amount of data can be executed as background jobs. The following steps should be used: 1. Start executing mutations as background job 2. Monitor the status of job execution via executing `job` query. There are three statuses: `EXECUTING`, `FAILED` and `COMPLETED` 3. Execute `job` query again if status still equals to `EXECUTING` 4. `actions` will be populated with data when job status equals to `COMPLETED` The job results will be available in \~30 minutes. Start background job defining actions to be executed in background ```graphql theme={null} mutation { features { executeAsBackgroundJob { jobId actions { createBatch(data: [ {name: "Feature 1"} {name: "Feature 2"} ]) {message} } } } } ``` The result contains jobId to monitor job status ```json theme={null} { "data": { "features": { "executeAsBackgroundJob": { "jobId": "6fc2d2b3-7b31-4511-b829-99a8cefb36b3", "actions": null } } } } ``` Monitor job status using jobId ```graphql theme={null} { job(id: "6fc2d2b3-7b31-4511-b829-99a8cefb36b3") { status message actions { actionName result { message entities {id} } } } } ``` The actions will be populated when status equals to "COMPLETED" ```json theme={null} { "data": { "job": { "status": "COMPLETED", "message": "Completed successfully", "actions": [ { "actionName": "createBatch", "result": { "message": "Create: 2 Features added", "entities": [ { "id": "9d7deab7-a72d-4e09-adde-f888ca455fbe" }, { "id": "2c8f16ce-c9b6-4273-b74e-7e924a308a1f" } ] } } ] } } } ``` ## Use paging for mutations Use `limit`, `offset` and `orderBy` in case of executing huge operational sets since it is required a lot of processing time and timeouts may be a problem. Please find an example on how to set sprint to stories by execution of the mutation with limit several times to avoid facing timeout. The main idea to update limited set of database until it has records which should be updated. In our case we are updating stories until some of them don't have sprint. Execute mutation with limit for first time: ```graphql theme={null} mutation { stories(limit: 10, sprint: {id: {isNull: true}}) { update(sprint: {limit: 1, orderBy: {dates: {start: ASC}}}) {message} countOfEntities } } ``` Result contains 10 entities. So we will need to execute it again. ```json theme={null} { "data": { "stories": { "update": { "message": "Update: 10 Stories updated" }, "countOfEntities": 10 } } } ``` Execute the same mutation again with the same limit: ```graphql theme={null} mutation { stories(limit: 10, sprint: {id: {isNull: true}}) { update(sprint: {limit: 1, orderBy: {dates: {start: ASC}}}) {message} countOfEntities } } ``` Looks like no need to execute again since count of processed less than limit (10) ```json theme={null} { "data": { "stories": { "update": { "message": "Update: 4 Stories updated" }, "countOfEntities": 4 } } } ``` # Overview Source: https://developers.fibery.com/guides/graphql/overview Get started with the Fibery GraphQL API. The Fibery GraphQL API provides a way to integrate Fibery with your external systems and automate routine tasks. It's capable of performing most of the tasks at hand: * Read, create, update and delete Entities in Databases. * Execute a variety of additional actions. * Work with rich text Fields. You can find more information about GraphQL basics [by this link](https://graphql.org/?utm_source=Fibery\&utm_medium=iframely "https://graphql.org/?utm_source=Fibery\&utm_medium=iframely"). Non-ASCII or non-English characters in field or database names will be transliterated to English. Every Fibery Space has its own GraphQL endpoint, which can be found at `https://YOUR_ACCOUNT.fibery.io/api/graphql/space/YOUR_SPACE` or the list of all your space's end-points can be found at `https://YOUR_ACCOUNT.fibery.io/api/graphql` image.png By opening space's end-point in your browser you will find a graphical interactive in-browser GraphQL IDE. Here you can explorer space's GraphQL documentation and execute your GraphQL queries. g-explorer-68787e96.gif To read or edit data, you need to send a POST JSON request to the `https://YOUR_ACCOUNT.fibery.io/api/graphql/space/YOUR_SPACE` endpoint from your code. # GraphQL queries Source: https://developers.fibery.com/guides/graphql/queries Learn how to query data using the Fibery GraphQL API. # Queries The list of entities can be retrieved from the database by using `find` query which is defined for every database for each space. For example `findBugs`, `findEmployees`. Find more information about GraphQL queries [here](https://graphql.org/learn/queries). g-find-list-64bc75cd.gif ## **List of entities** Use `findXXX` without arguments to retrieve all records, but note that there is a limit of 100 by default, so use offset and limit to retrieve data page by page if it is required. ```graphql theme={null} { findFeatures { id name state { name } } } ``` Use Docs β†’ Query section to explore possible fields selection. Screenshot 2024-01-12 at 10.04.00 AM.png Curl: ```bash theme={null} curl -X POST https://YOUR_ACCOUNT.fibery.io/api/graphql/space/Software_Development \ -H "Authorization: Token YOUR_TOKEN" \ -H "Content-Type: application/json" \ -d '{"query":"{findBugs{id,name,state{name}}}"}' ``` JavaScript: ```javascript theme={null} import {config} from 'dotenv'; config(); import fetch from 'node-fetch'; const YOUR_SPACE_ENDPOINT = `https://YOUR_ACCOUNT.fibery.io/api/graphql/space/Software_Development`; const YOUR_TOKEN = process.env[`YOUR_TOKEN`]; (async () => { const query = `{findBugs{id,name,state{name}}}`; const response = await fetch(YOUR_SPACE_ENDPOINT, { method: 'POST', body: JSON.stringify({query}), headers: { 'Content-Type': `application/json`, 'Authorization': `Token ${YOUR_TOKEN}`, } }); const result = await response.json(); console.log(JSON.stringify(result)); })(); ``` Output ```json theme={null} { "data": { "findBugs": [ { "id": "b3814a20-e261-11e8-80ea-7f915d8486b5", "name": "🐞 The first ever bug", "state": { "name": "Done" } }, { "id": "fa39df10-912b-11eb-a0bf-cb515797cdf8", "name": "Nasty bug from the trenches", "state": { "name": "To Do" } } ] } } ``` ## Filtering There is a variety of filtering capabilities for each database including filtering by one-to-one fields or inner lists content. Filters can be applied by providing filtering arguments for find queries. The filter operators available can be discovered through autocomplete while creating a query in GraphiQL. g-filters-000097e3.gif Filtering by native fields ```graphql theme={null} { findBugs( name: {contains: "disaster"} state: {name: {in: ["Open", "Done"]}} ) { id name state { name } } } ``` Filtering by one-to-one fields Find Open and Done bugs, for example: ```graphql theme={null} { findBugs( state: {name: {in: ["Open", "Done"]}} ) { id name state { name } } } ``` Find High priority bugs: ```graphql theme={null} { findBugs(orderBy: {rank: ASC}, priority: {name: {is: "High"}}) { name, } } ``` Filtering by many fields (AND statement is used) ```graphql theme={null} { findBugs( release: {startDate: {isNull: false}, name: {contains: "1.0"}}, state: {name: {in: ["Open", "Done"]}} ) { id name release { startDate name } state { name } } } ``` **String** filtering operators ```javascript theme={null} is: String isNot: String contains: String notContains: String greater: String greaterOrEquals: String less: String lessOrEquals: String in: [String] notIn: [String] isNull: Boolean ``` **Int** filtering operators ```javascript theme={null} is: Int isNot: Int greater: Int greaterOrEquals: Int less: Int lessOrEquals: Int in: [Int] notIn: [Int] isNull: Boolean ``` **Float** filtering operators ```javascript theme={null} is: Float isNot: Float greater: Float greaterOrEquals: Float less: Float lessOrEquals: Float in: [Float] notIn: [Float] isNull: Boolean ``` **Boolean** filtering operators ```javascript theme={null} is: Boolean isNull: Boolean ``` **ID** filtering operators ```javascript theme={null} is: ID isNot: ID in: [ID] notIn: [ID] isNull: Boolean ``` ### **Filtering by inner lists** The database can be filtered by content of inner list, but it is a bit different from filtering by one-to-one or native fields. For example the query to the left allows to find releases which contains bugs in "Open" state or with effort greater than 0. The following operators can be used for filtering database by inner list: ```javascript theme={null} isEmpty: Boolean contains: [InnerListDbFilter] // AND statement containsAny: [InnerListDbFilter] // OR statement notContains: [InnerListDbFilter] // AND statement notContainsAny: [InnerListDbFilter] // OR statement ``` Filtering by inner lists: ```graphql theme={null} { findReleases( bugs: { containsAny: [ {state: {name: {is: "Open"}}} {effort: {greater: 0}} ] }) { name bugs { name state { name } } } } ``` ### **Filtering inner lists** The inner list of database can be filtered in the same way the database filtered. For example if you want to show only "To Do" bugs for releases: Sample of filtering inner list ```graphql theme={null} { findReleases { name bugs(state: {name: {is: "To Do"}}) { name state { name } } } } ``` ## **Sorting** The database or content of inner lists of the database can be sorted using `orderBy` argument which can be applied for native fields or one-to-one properties. ```graphql theme={null} { findReleases( bugs: {isEmpty: false} orderBy: { releaseDate: DESC } ) { name releaseDate bugs( orderBy: { name: ASC createdBy: {email: ASC} } ) { name state { name } } } } ``` ## **Rich fields and comments** You can download content of rich text fields or comments in four formats: `jsonString`, `text`, `md`, `html`. ```graphql theme={null} { findBugs { name stepsToReproduce { text } comments { md } } } ``` Output ```json theme={null} { "data": { "findBugs": [ { "name": "🐞 The first ever bug", "stepsToReproduce": { "text": "Open up the Mark II\n\n\nCheck all the relays one-by-one\n\n\nFind a little naughty moth" }, "comments": [ { "md": "Please fix ASAP" } ] } ] } } ``` ## **File fields** You can query for public file url which will be valid for 60 minutes using `url` ```graphql theme={null} { findBugs { name files { name, url, urlExpiresAt } } } ``` Output ```json theme={null} { "data": { "findBugs": [ { "name": "🐞 The first ever bug", "files": [ { "name": "Screenshot 2025-12-29 at 13.31.48.png", "url": "https://d1....", "urlExpiresAt": "2026-01-22T14:19:02.571Z" } ] } ] } } ``` ## **Paging and limits** By default, find database query returns 100 records. The default can be changed by setting `limit` argument. Use `offset` argument to retrieve next page if the current page contains 100 records (or equals to limit value). Retrieve first page (limit is 3) ```graphql theme={null} { findBugs(limit: 3) { name } } ``` Retrieve second page (limit: 3, offset: 3). Retrieve only if first page size equals to 3 ```graphql theme={null} { findBugs(limit: 3, offset: 3) { name } } ``` ## **Aliases** GraphQL aliases can be used for find query if you would like to get separated results or as alternative to OR statement. Using aliases: ```graphql theme={null} { todo: findBugs(state: {name: {is: "To Do"}}) { name state { name } } done: findBugs(state: {name: {is: "Done"}}) { name state { name } } } ``` Output: ```json theme={null} { "data": { "todo": [ { "name": "Nasty bug from the trenches", "state": { "name": "To Do" } } ], "done": [ { "name": "🐞 The first ever bug", "state": { "name": "Done" } } ] } } ``` # Using the IDE Source: https://developers.fibery.com/guides/graphql/using-the-ide Explore the Fibery GraphQL schema in the built-in IDE. GraphQL is a query language for APIs and a runtime for fulfilling those queries with your existing data. The good news is that Fibery GraphQL IDE can be used by non-tech users to retrieve and modify the database records in a quick and simple way since it has autocomplete and easy to understand language. 2022-07-22 11.11.43.gif ## How to start Every space has separate GraphQL API endpoint. The list of available space API endpoints can be found by following the link: `{your fibery host}/api/graphql` image.png By opening space's end-point in your browser you will find a graphical interactive in-browser GraphQL IDE. Here you can explorer space's GraphQL documentation and execute your GraphQL queries. g-explorer.gif Now you are ready to find records in your database. ## How to find database records The records can be queried using query which starts from `find` For example: ```graphql theme={null} { findBugs(name: {contains: "first"}) { id name state { name } assignees { name } } } ``` image.png Read more about queries including sorting and paging in [GraphQL queries](/guides/graphql/queries). ## How to modify or create database records There are multiple operations available for modifying database records. These operations can be performed for found entities by provided filter or for created records in corresponding database. g-mutations.gif Find below an example of operations which can be performed for created record. In this example new bug created, assigned to author of API call, description with a template content is set to bug description. ```graphql theme={null} mutation { stories { create(name: "Super Bug") { message } assignToMe { message } appendContentToDescription( value: "This is a description of *{{Name}}*" ) {message} } } ``` image.png For creating multiple records batch operation command **createBatch** can be used. ```graphql theme={null} mutation { stories { createBatch(data: [ {name: "Bug 1"} {name: "Bug 2"} ]) { entities { id } } assignToMe { message } appendContentToDescription(value: "TBD") { message } } } ``` Find below the example of operations which can be performed for found records by provided filter as params to root node of mutation. Bugs with word β€œfirst” in name are moved into β€œIn Progress” state, sprint is unlinked, found bugs are assigned to author of API call and the owner of found stories is notified. ```graphql theme={null} mutation { bugs(name: {contains: "first"}) { update(state: {name: {is: "In Progress"}}) { message entities { id } } unlinkSprint { message } assignToMe { message } notifyCreatedBy(subject: "Assigned to Aleh") { message } } } ``` image.png Read more about [GraphQL mutations](/guides/graphql/mutations). ## Samples ### Retrieve tasks assigned to current user ```graphql theme={null} { findTasks(assignees: {contains: {id: {is: "$my-id"}}}) { name, description { md } } } ``` ### Retrieve content of rich fields and documents You can download content of rich fields or comments in four formats: `jsonString`, `text`, `md`, `html`. ```graphql theme={null} { findFeatures { name, description { text } } } ``` ### Batch update of multi select field Set values "One" and "Two" to multi select field. ```graphql theme={null} mutation { tasks(multiSelect: {isEmpty: true}) { update( multiSelect: {name: {in: ["One", "Two"]}} ) { message } } } ``` ### Assign tasks to current user ```graphql theme={null} mutation { tasks(assignees: {notContains: {id: {is: "$my-id"}}}) { assignToMe {message} } } ``` ### Change value of rich field Let's assume `Feature` database has rich field `Description`. Rich field can be updated using methods below. [Markdown Templates](https://the.fibery.io/@public/User_Guide/Guide/Markdown-Templates-53) are supported as well. ```graphql theme={null} mutation { features(id: {is: "ABC"}) { overwriteDescription(value: "rewrite {{NAME}} desc") {message} appendContentToDescription(value: "text to append") {message} prependContentToDescription(value: "text to prepend") {message} } } ``` ### Clear values with GraphQL ```graphql theme={null} mutation { stories(id: {isNull: false}) { update(myDate: null) {message} } } ``` # Collections Source: https://developers.fibery.com/guides/http-api/collections Learn how to manage collection, single-select, and multi-select Fields using the Fibery API. The API uses `type` for Database and `app` for Space. See [Terminology](/guides/general/terminology#api-naming). ## Update entity collection Field Add already existing Entities to an entity collection Field by providing their `fibery/id`. Remove Entities from the collection in a similar way. Get `fibery/id` either via API or by opening the relevant Entity on UI and exploring the command response in browser's Network tab. `Cricket/Player` Database used as an example | Field name | Field type | | ---------------------- | ----------------------- | | `fibery/id` | `fibery/uuid` | | `Cricket/Former Teams` | entity collection Field | ### Add Add two existing Teams to Player's "Former Teams" entity collection Field (if team already exists in the collection then team will be ignored during addition): ```javascript JavaScript theme={null} const response = await fetch('https://YOUR_ACCOUNT.fibery.io/api/commands', { method: 'POST', headers: { 'Authorization': 'Token YOUR_TOKEN', 'Content-Type': 'application/json' }, body: JSON.stringify({ command: 'fibery.entity/add-collection-items', args: { type: 'Cricket/Player', field: 'Cricket/Former Teams', entity: { '216c2a00-9752-11e9-81b9-4363f716f666': [ '0a3ae1c0-97fa-11e9-81b9-4363f716f666', '17af8db0-97fa-11e9-81b9-4363f716f666' ] } } }) }); const data = await response.json(); ``` ```bash cURL theme={null} curl -X POST https://YOUR_ACCOUNT.fibery.io/api/commands \ -H 'Authorization: Token YOUR_TOKEN' \ -H 'Content-Type: application/json' \ -d ' { "command": "fibery.entity/add-collection-items", "args": { "type": "Cricket/Player", "field": "Cricket/Former Teams", "entity": { "216c2a00-9752-11e9-81b9-4363f716f666": [ "0a3ae1c0-97fa-11e9-81b9-4363f716f666", "17af8db0-97fa-11e9-81b9-4363f716f666" ] } } } ' ``` Response: ```json theme={null} { "success": true, "result": "ok" } ``` ### Remove Remove two Teams from Player's "Former Teams" entity collection Field (if team doesn't exist in the collection then team will be ignored during removing): ```javascript JavaScript theme={null} const response = await fetch('https://YOUR_ACCOUNT.fibery.io/api/commands', { method: 'POST', headers: { 'Authorization': 'Token YOUR_TOKEN', 'Content-Type': 'application/json' }, body: JSON.stringify({ command: 'fibery.entity/remove-collection-items', args: { type: 'Cricket/Player', field: 'Cricket/Former Teams', entity: { '216c2a00-9752-11e9-81b9-4363f716f666': [ '0a3ae1c0-97fa-11e9-81b9-4363f716f666', '17af8db0-97fa-11e9-81b9-4363f716f666' ] } } }) }); const data = await response.json(); ``` ```bash cURL theme={null} curl -X POST https://YOUR_ACCOUNT.fibery.io/api/commands \ -H 'Authorization: Token YOUR_TOKEN' \ -H 'Content-Type: application/json' \ -d ' { "command": "fibery.entity/remove-collection-items", "args": { "type": "Cricket/Player", "field": "Cricket/Former Teams", "entity": { "216c2a00-9752-11e9-81b9-4363f716f666": [ "0a3ae1c0-97fa-11e9-81b9-4363f716f666", "17af8db0-97fa-11e9-81b9-4363f716f666" ] } } } ' ``` Response: ```json theme={null} { "success": true, "result": "ok" } ``` ### Set Replace two Teams from Player's "Former Teams" entity collection Field with new team. Replace means deletion of any existing collection items and adding new items. ```javascript JavaScript theme={null} const response = await fetch('https://YOUR_ACCOUNT.fibery.io/api/commands', { method: 'POST', headers: { 'Authorization': 'Token YOUR_TOKEN', 'Content-Type': 'application/json' }, body: JSON.stringify({ command: 'fibery.entity/set-collection-items', args: { type: 'Cricket/Player', field: 'Cricket/Former Teams', entity: { '216c2a00-9752-11e9-81b9-4363f716f666': [ '02007d5e-b3d9-44c3-83de-6b562870f120' ] } } }) }); const data = await response.json(); ``` ```bash cURL theme={null} curl -X POST https://YOUR_ACCOUNT.fibery.io/api/commands \ -H 'Authorization: Token YOUR_TOKEN' \ -H 'Content-Type: application/json' \ -d ' { "command": "fibery.entity/set-collection-items", "args": { "type": "Cricket/Player", "field": "Cricket/Former Teams", "entity": { "216c2a00-9752-11e9-81b9-4363f716f666": [ "02007d5e-b3d9-44c3-83de-6b562870f120" ] } } } ' ``` Response: ```json theme={null} { "success": true, "result": "ok" } ``` ### Reset Resets "Former Teams" entity collection Field with new team. ```javascript JavaScript theme={null} const response = await fetch('https://YOUR_ACCOUNT.fibery.io/api/commands', { method: 'POST', headers: { 'Authorization': 'Token YOUR_TOKEN', 'Content-Type': 'application/json' }, body: JSON.stringify({ command: 'fibery.entity/reset-collection-items', args: { type: 'Cricket/Player', field: 'Cricket/Former Teams', entity: '216c2a00-9752-11e9-81b9-4363f716f666' } }) }); const data = await response.json(); ``` ```bash cURL theme={null} curl -X POST https://YOUR_ACCOUNT.fibery.io/api/commands \ -H 'Authorization: Token YOUR_TOKEN' \ -H 'Content-Type: application/json' \ -d ' { "command": "fibery.entity/reset-collection-items", "args": { "type": "Cricket/Player", "field": "Cricket/Former Teams", "entity": "216c2a00-9752-11e9-81b9-4363f716f666" } } ' ``` Response: ```json theme={null} { "success": true, "result": "ok" } ``` ## Update single-select and multi-select Fields A single-select Field is an entity Field β€” each option is an Entity with its own `fibery/id`. Update it via `fibery.entity/update`, the same way as any entity Field. A multi-select Field is an entity collection Field β€” each option is an Entity with its own `fibery/id`. Update it via collection commands (`add-collection-items`, `remove-collection-items`, `set-collection-items`, `reset-collection-items`), the same way as any entity collection Field. Get an option's `fibery/id` either via API or by opening the Entity on UI and exploring the command response in browser's Network tab. `Cricket/Player` Database used as an example | Field name | Field type | | ----------------------- | ------------- | | `Cricket/Batting Hand` | single-select | | `Cricket/Playing Roles` | multi-select | ### Set single-select Field Set `Cricket/Batting Hand` on a Player: ```javascript JavaScript theme={null} const response = await fetch('https://YOUR_ACCOUNT.fibery.io/api/commands', { method: 'POST', headers: { 'Authorization': 'Token YOUR_TOKEN', 'Content-Type': 'application/json' }, body: JSON.stringify({ command: 'fibery.entity/update', args: { type: 'Cricket/Player', entity: { 'fibery/id': '20f9b920-9752-11e9-81b9-4363f716f666', 'Cricket/Batting Hand': { 'fibery/id': 'b0ed1370-9747-11e9-9f03-fd937c4ecf3b' } } } }) }); const data = await response.json(); ``` ```bash cURL theme={null} curl -X POST https://YOUR_ACCOUNT.fibery.io/api/commands \ -H 'Authorization: Token YOUR_TOKEN' \ -H 'Content-Type: application/json' \ -d ' { "command": "fibery.entity/update", "args": { "type": "Cricket/Player", "entity": { "fibery/id": "20f9b920-9752-11e9-81b9-4363f716f666", "Cricket/Batting Hand": {"fibery/id": "b0ed1370-9747-11e9-9f03-fd937c4ecf3b"} } } } ' ``` Response: ```json theme={null} { "success": true, "result": { "fibery/id": "20f9b920-9752-11e9-81b9-4363f716f666" } } ``` ### Add options to multi-select Field Add two options to `Cricket/Playing Roles` on a Player: ```javascript JavaScript theme={null} const response = await fetch('https://YOUR_ACCOUNT.fibery.io/api/commands', { method: 'POST', headers: { 'Authorization': 'Token YOUR_TOKEN', 'Content-Type': 'application/json' }, body: JSON.stringify({ command: 'fibery.entity/add-collection-items', args: { type: 'Cricket/Player', field: 'Cricket/Playing Roles', entity: { '20f9b920-9752-11e9-81b9-4363f716f666': [ 'c1d8e4b0-9747-11e9-9f03-fd937c4ecf3b', 'c1d9f5c0-9747-11e9-9f03-fd937c4ecf3b' ] } } }) }); const data = await response.json(); ``` ```bash cURL theme={null} curl -X POST https://YOUR_ACCOUNT.fibery.io/api/commands \ -H 'Authorization: Token YOUR_TOKEN' \ -H 'Content-Type: application/json' \ -d ' { "command": "fibery.entity/add-collection-items", "args": { "type": "Cricket/Player", "field": "Cricket/Playing Roles", "entity": { "20f9b920-9752-11e9-81b9-4363f716f666": [ "c1d8e4b0-9747-11e9-9f03-fd937c4ecf3b", "c1d9f5c0-9747-11e9-9f03-fd937c4ecf3b" ] } } } ' ``` Response: ```json theme={null} { "success": true, "result": "ok" } ``` Remove, replace, or reset multi-select options the same way β€” see [Update entity collection Field](#update-entity-collection-field) for `fibery.entity/remove-collection-items`, `fibery.entity/set-collection-items`, and `fibery.entity/reset-collection-items`. # Create and update entities Source: https://developers.fibery.com/guides/http-api/create-update-entities Learn how to create, update, and delete Entities using the Fibery API. The API uses `type` for Database and `app` for Space. See [Terminology](/guides/general/terminology#api-naming). ## Create Entity Create Entities with primitive, single-select and entity Fields. Setting `fibery/id` is optional and might be useful for working with Entity right after creation. To set a single-select or an entity Field we'll need the target Entity's `fibery/id`. We can get `fibery/id` either via API (check [Query entities](/guides/http-api/query-entities)) or by opening the relevant Entity on UI and exploring the command response in browser's Network tab. Note that the target related Entity should already exist. Setting entity collection Fields on Entity creation is not supported. Instead we suggest updating entity collection Fields after the Entity is created β€” see [Collections](/guides/http-api/collections). Setting a rich text Field on creation is not possible either. Update rich text Field once the Entity is created β€” see [Rich text and comments](/guides/http-api/rich-text-and-comments). `Cricket/Player` Database used as an example | Field name | Field type | | ---------------------- | ----------------------- | | `fibery/id` | `fibery/uuid` | | `fibery/public-id` | `fibery/text` | | `Cricket/Name` | `fibery/text` | | `Cricket/Full Name` | `fibery/text` | | `Cricket/Born` | `fibery/date` | | `Cricket/Youth Career` | `fibery/date-range` | | `Cricket/Shirt Number` | `fibery/int` | | `Cricket/Height` | `fibery/decimal` | | `Cricket/Retired` | `fibery/bool` | | `Cricket/Batting Hand` | single-select | | `Cricket/Current Team` | entity Field | | `Cricket/Former Teams` | entity collection Field | ```javascript JavaScript theme={null} const response = await fetch('https://YOUR_ACCOUNT.fibery.io/api/commands', { method: 'POST', headers: { 'Authorization': 'Token YOUR_TOKEN', 'Content-Type': 'application/json' }, body: JSON.stringify({ command: 'fibery.entity/create', args: { type: 'Cricket/Player', entity: { 'fibery/id': 'd17390c4-98c8-11e9-a2a3-2a2ae2dbcce4', 'Cricket/Name': 'Curtly Ambrose', 'Cricket/Full Name': 'Curtly Elconn Lynwall Ambrose', 'Cricket/Born': '1963-09-21', 'Cricket/Youth Career': { start: '1985-01-01', end: '1986-01-01' }, 'Cricket/Shirt Number': 1, 'Cricket/Height': '2.01', 'Cricket/Retired': true, 'Cricket/Batting Hand': { 'fibery/id': 'b0ed3a80-9747-11e9-9f03-fd937c4ecf3b' } } } }) }); const data = await response.json(); ``` ```bash cURL theme={null} curl -X POST https://YOUR_ACCOUNT.fibery.io/api/commands \ -H 'Authorization: Token YOUR_TOKEN' \ -H 'Content-Type: application/json' \ -d ' { "command": "fibery.entity/create", "args": { "type": "Cricket/Player", "entity": { "fibery/id": "d17390c4-98c8-11e9-a2a3-2a2ae2dbcce4", "Cricket/Name": "Curtly Ambrose", "Cricket/Full Name": "Curtly Elconn Lynwall Ambrose", "Cricket/Born": "1963-09-21", "Cricket/Youth Career": { "start": "1985-01-01", "end": "1986-01-01" }, "Cricket/Shirt Number": 1, "Cricket/Height": "2.01", "Cricket/Retired": true, "Cricket/Batting Hand": {"fibery/id": "b0ed3a80-9747-11e9-9f03-fd937c4ecf3b"} } } } ' ``` Result with all primitive Fields, single-selects and entity Fields: ```json theme={null} { "success": true, "result": { "Cricket/Height": "2.01", "fibery/modification-date": "2019-06-27T10:44:53.860Z", "Cricket/Born": "1963-09-21", "fibery/id": "d17390c4-98c8-11e9-a2a3-2a2ae2dbcce4", "fibery/creation-date": "2019-06-27T10:44:53.860Z", "fibery/created-by": { "fibery/id": "fe1db100-3779-11e9-9162-04d77e8d50cb" }, "fibery/rank": 5674304923033269, "Cricket/Shirt Number": 1, "Cricket/Full Name": "Curtly Elconn Lynwall Ambrose", "fibery/public-id": "6", "Cricket/Retired": true, "Cricket/Current Team": null, "Cricket/Batting Hand": { "fibery/id": "b0ed3a80-9747-11e9-9f03-fd937c4ecf3b" }, "Cricket/Bio": { "fibery/id": "019dd3cd-3810-702d-857b-20fb92d60268" }, "Cricket/Youth Career": { "start": "1985-01-01", "end": "1986-01-01" }, "Cricket/Name": "Curtly Ambrose" } } ``` ## Update Entity Update primitive, single-select and entity Fields this way. For updating entity collection Fields, see [Collections](/guides/http-api/collections). To update a single-select or an entity Field, we'll need the target Entity's `fibery/id`. We can get `fibery/id` either via API or by opening the relevant Entity on UI and exploring the command response in browser's Network tab. Note that the target Entity should already exist. `Cricket/Player` Database used as an example | Field name | Field type | | ---------------------- | ----------------------- | | `fibery/id` | `fibery/uuid` | | `fibery/public-id` | `fibery/text` | | `Cricket/Name` | `fibery/text` | | `Cricket/Full Name` | `fibery/text` | | `Cricket/Born` | `fibery/date` | | `Cricket/Youth Career` | `fibery/date-range` | | `Cricket/Shirt Number` | `fibery/int` | | `Cricket/Height` | `fibery/decimal` | | `Cricket/Retired` | `fibery/bool` | | `Cricket/Batting Hand` | single-select | | `Cricket/Current Team` | entity Field | | `Cricket/Former Teams` | entity collection Field | ```javascript JavaScript theme={null} const response = await fetch('https://YOUR_ACCOUNT.fibery.io/api/commands', { method: 'POST', headers: { 'Authorization': 'Token YOUR_TOKEN', 'Content-Type': 'application/json' }, body: JSON.stringify({ command: 'fibery.entity/update', args: { type: 'Cricket/Player', entity: { 'fibery/id': '20f9b920-9752-11e9-81b9-4363f716f666', 'Cricket/Full Name': 'Virat "Chikoo" Kohli', 'Cricket/Current Team': { 'fibery/id': 'd328b7b0-97fa-11e9-81b9-4363f716f666' } } } }) }); const data = await response.json(); ``` ```bash cURL theme={null} curl -X POST https://YOUR_ACCOUNT.fibery.io/api/commands \ -H 'Authorization: Token YOUR_TOKEN' \ -H 'Content-Type: application/json' \ -d ' { "command": "fibery.entity/update", "args": { "type": "Cricket/Player", "entity": { "fibery/id": "20f9b920-9752-11e9-81b9-4363f716f666", "Cricket/Full Name": "Virat \"Chikoo\" Kohli", "Cricket/Current Team": {"fibery/id": "d328b7b0-97fa-11e9-81b9-4363f716f666"} } } } ' ``` Response: ```json theme={null} { "success": true, "result": { "fibery/id": "20f9b920-9752-11e9-81b9-4363f716f666" } } ``` ## Delete Entity Delete an Entity by providing its Database and `fibery/id`. ```javascript JavaScript theme={null} const response = await fetch('https://YOUR_ACCOUNT.fibery.io/api/commands', { method: 'POST', headers: { 'Authorization': 'Token YOUR_TOKEN', 'Content-Type': 'application/json' }, body: JSON.stringify({ command: 'fibery.entity/delete', args: { type: 'Cricket/Player', entity: { 'fibery/id': '93648510-9907-11e9-acf1-fd0d502cdd20' } } }) }); const data = await response.json(); ``` ```bash cURL theme={null} curl -X POST https://YOUR_ACCOUNT.fibery.io/api/commands \ -H 'Authorization: Token YOUR_TOKEN' \ -H 'Content-Type: application/json' \ -d ' { "command": "fibery.entity/delete", "args": { "type": "Cricket/Player", "entity": {"fibery/id": "93648510-9907-11e9-acf1-fd0d502cdd20"} } } ' ``` Response: ```json theme={null} { "success": true, "result": null } ``` # Databases Source: https://developers.fibery.com/guides/http-api/databases Learn how to create and manage Databases using the Fibery API. A Database is a template for a kind of Entity: Bugs, Teams, Objectives, etc. It consists of metadata and Fields. 2024-01-12 12.43.42.gif The API uses `type` for Database and `app` for Space. See [Terminology](/guides/general/terminology#api-naming). ## Database and Field permissions Imagine you've got a Database `Task` with a Field called `Effort`. Here's how permissions apply depending on `secured?` parameter:
**Task `secured?`**
❌ βœ…
**Effort `secured?`** ❌ Everyone has access to all fields Everyone has access to Effort, but not to other `secured?` fields
βœ… Everyone has access to all fields Everyone has access to Task's non-secured Fields like Id, but permissions are applied to Effort
## Create Database Every Database is a part of some Space. If the Database's Space does not exist yet, create or install the Space. To create a fully functional Database, we'll execute two commands: 1. `schema.type/create` to create a Database with at least five mandatory primitive Fields: * `fibery/id` * `fibery/public-id` * `fibery/creation-date` * `fibery/modification-date` * `${space}/name` 2. `fibery.app/install-mixins` to be able to prioritize the Database's Entities. For auxiliary Databases, that are hidden from `Workspace Map` screen, `${space}/name` Field and `rank` Mixin are optional. Just skip these parts when creating a Database. Auxiliary Databases might be useful as an Entity-based storage β€” that's how our User's favourite pages and recent items work, for example. ```javascript JavaScript theme={null} const response = await fetch('https://YOUR_ACCOUNT.fibery.io/api/commands', { method: 'POST', headers: { 'Authorization': 'Token YOUR_TOKEN', 'Content-Type': 'application/json' }, body: JSON.stringify({ command: 'fibery.schema/batch', args: { commands: [ { command: 'schema.type/create', args: { 'fibery/name': 'Cricket/Player', 'fibery/meta': { 'fibery/domain?': true, 'fibery/secured?': true, 'ui/color': '#F7D130' }, 'fibery/fields': [ { 'fibery/name': 'Cricket/name', 'fibery/type': 'fibery/text', 'fibery/meta': { 'fibery/secured?': false, 'ui/title?': true } }, { 'fibery/name': 'fibery/id', 'fibery/type': 'fibery/uuid', 'fibery/meta': { 'fibery/secured?': false, 'fibery/id?': true, 'fibery/readonly?': true } }, { 'fibery/name': 'fibery/public-id', 'fibery/type': 'fibery/text', 'fibery/meta': { 'fibery/secured?': false, 'fibery/public-id?': true, 'fibery/readonly?': true } }, { 'fibery/name': 'fibery/creation-date', 'fibery/type': 'fibery/date-time', 'fibery/meta': { 'fibery/secured?': false, 'fibery/creation-date?': true, 'fibery/readonly?': true, 'fibery/default-value': '$now' } }, { 'fibery/name': 'fibery/modification-date', 'fibery/type': 'fibery/date-time', 'fibery/meta': { 'fibery/modification-date?': true, 'fibery/required?': true, 'fibery/readonly?': true, 'fibery/default-value': '$now', 'fibery/secured?': false } }, { 'fibery/name': 'user/salary', 'fibery/type': 'fibery/int', 'fibery/meta': { 'fibery/secured?': true } } ] } }, { command: 'fibery.app/install-mixins', args: { types: { 'Cricket/Player': ['fibery/rank-mixin'] } } } ] } }) }); const data = await response.json(); ``` ```bash cURL theme={null} curl -X POST https://YOUR_ACCOUNT.fibery.io/api/commands \ -H 'Authorization: Token YOUR_TOKEN' \ -H 'Content-Type: application/json' \ -d ' { "command": "fibery.schema/batch", "args": { "commands": [ { "command": "schema.type/create", "args": { "fibery/name": "Cricket/Player", "fibery/meta": { "fibery/domain?": true, "fibery/secured?": true, "ui/color": "#F7D130" }, "fibery/fields": [ { "fibery/name": "Cricket/name", "fibery/type": "fibery/text", "fibery/meta": { "fibery/secured?": false, "ui/title?": true } }, { "fibery/name": "fibery/id", "fibery/type": "fibery/uuid", "fibery/meta": { "fibery/secured?": false, "fibery/id?": true, "fibery/readonly?": true } }, { "fibery/name": "fibery/public-id", "fibery/type": "fibery/text", "fibery/meta": { "fibery/secured?": false, "fibery/public-id?": true, "fibery/readonly?": true } }, { "fibery/name": "fibery/creation-date", "fibery/type": "fibery/date-time", "fibery/meta": { "fibery/secured?": false, "fibery/creation-date?": true, "fibery/readonly?": true, "fibery/default-value": "$now" } }, { "fibery/name": "fibery/modification-date", "fibery/type": "fibery/date-time", "fibery/meta": { "fibery/modification-date?": true, "fibery/required?": true, "fibery/readonly?": true, "fibery/default-value": "$now", "fibery/secured?": false } }, { "fibery/name": "user/salary", "fibery/type": "fibery/int", "fibery/meta": { "fibery/secured?": true } } ] } }, { "command": "fibery.app/install-mixins", "args": { "types": { "Cricket/Player": [ "fibery/rank-mixin" ] } } } ] } } ' ``` Response: ```json theme={null} { "success": true, "result": "ok" } ``` ### Command parameters | Parameter (required in bold) | Default | Description | Example | | ---------------------------- | -------------- | ------------------------------------------------------------------------------ | ------------------ | | **`fibery/name`** | | Database name in `${space}/${name}` format | `CRM/Lead` | | `fibery/id` | Auto-generated | UUID | `fd5d9550-3779...` | | meta.`fibery/domain?` | false | Domain Databases are available as cards on Views | true | | meta.**`fibery/secured?`** | | [Permissions](#database-and-field-permissions) apply to secured Databases only | true | | meta.`ui/color?` | #000000 | HEX color to use in Entity badges | #F7D130 | | meta.**`fibery/fields`** | | Array of [Fields](/guides/http-api/fields) including 5 primitive ones above | | ## Rename Database ```javascript JavaScript theme={null} const response = await fetch('https://YOUR_ACCOUNT.fibery.io/api/commands', { method: 'POST', headers: { 'Authorization': 'Token YOUR_TOKEN', 'Content-Type': 'application/json' }, body: JSON.stringify({ command: 'fibery.schema/batch', args: { commands: [ { command: 'schema.type/rename', args: { 'from-name': 'Cricket/Referee', 'to-name': 'Cricket/Umpire' } } ] } }) }); const data = await response.json(); ``` ```bash cURL theme={null} curl -X POST https://YOUR_ACCOUNT.fibery.io/api/commands \ -H 'Authorization: Token YOUR_TOKEN' \ -H 'Content-Type: application/json' \ -d ' { "command": "fibery.schema/batch", "args": { "commands": [ { "command": "schema.type/rename", "args": { "from-name": "Cricket/Referee", "to-name": "Cricket/Umpire" } } ] } } ' ``` Response: ```json theme={null} { "success": true, "result": "ok" } ``` ### **Command parameters** | Parameter (required in bold) | Description | Example | | ---------------------------- | -------------------------------------------------- | ----------------- | | **`from-name`** | Current Database name in `${space}/${name}` format | `Cricket/Referee` | | **`to-name`** | New Database name in `${space}/${name}` format | `Cricket/Umpire` | ## **Delete Database** ```javascript JavaScript theme={null} const response = await fetch('https://YOUR_ACCOUNT.fibery.io/api/commands', { method: 'POST', headers: { 'Authorization': 'Token YOUR_TOKEN', 'Content-Type': 'application/json' }, body: JSON.stringify({ command: 'fibery.schema/batch', args: { commands: [ { command: 'schema.type/delete', args: { name: 'Cricket/Umpire', 'delete-entities?': true, 'delete-related-fields?': true } } ] } }) }); const data = await response.json(); ``` ```bash cURL theme={null} curl -X POST https://YOUR_ACCOUNT.fibery.io/api/commands \ -H 'Authorization: Token YOUR_TOKEN' \ -H 'Content-Type: application/json' \ -d ' { "command": "fibery.schema/batch", "args": { "commands": [ { "command": "schema.type/delete", "args": { "name": "Cricket/Umpire", "delete-entities?": true, "delete-related-fields?": true } } ] } } ' ``` Response: ```json theme={null} { "success": true, "result": "ok" } ``` ### Command parameters | Parameter (required in bold) | Default | Description | Example | | ---------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------ | ---------------- | | **`name`** | | Database name in `${space}/${name}` format | `Cricket/Umpire` | | `delete-entities?` | false | Delete all Entities of this Database? See the behavior in the table below. | true | | `delete-related-fields?` | false | Delete all related Fields like `Criket/Favourite Umpire` in `Cricket/Player`? See the behavior in the table below. | true | ### `delete?` parameter behavior
**`delete?` parameter**
**false** **true**
**Entities (or related Fields)** **don't exist** Database is deleted Database is deleted
**exist** Error is thrown Database and Entities (or related Fields) are deleted
Related single-select `enum` Databases are not deleted even with `delete-related-fields?` enabled. Delete these Databases separately the same way you delete the original Database. # FAQ Source: https://developers.fibery.com/guides/http-api/faq Frequently asked questions about the Fibery HTTP API. If this page doesn't answer your question, please contact us in the support chat. ## How to update the avatar with a URL to an image? Avatars is the same file collection as the files, so check the [Files](/guides/http-api/files) guide. You can find a [nice discussion in our community ](https://community.fibery.io/t/manipulating-the-avatar-via-automation-script/3918 "https://community.fibery.io/t/manipulating-the-avatar-via-automation-script/3918")πŸ™‚ ## How to update the Icon Field? The Icon Field is exposed as `icon/icon`. Update it with `fibery.entity/update` like any other primitive Field. The value is the icon name wrapped in colons β€” `:smile:`, `:heart:`, `:rocket:`. Browse the available icons in the [Unicons set](https://iconscout.com/unicons). ```javascript JavaScript theme={null} const response = await fetch('https://YOUR_ACCOUNT.fibery.io/api/commands', { method: 'POST', headers: { 'Authorization': 'Token YOUR_TOKEN', 'Content-Type': 'application/json' }, body: JSON.stringify({ command: 'fibery.entity/update', args: { type: 'Cricket/Player', entity: { 'fibery/id': '20f9b920-9752-11e9-81b9-4363f716f666', 'icon/icon': ':smile:' } } }) }); const data = await response.json(); ``` ```bash cURL theme={null} curl -X POST https://YOUR_ACCOUNT.fibery.io/api/commands \ -H 'Authorization: Token YOUR_TOKEN' \ -H 'Content-Type: application/json' \ -d ' { "command": "fibery.entity/update", "args": { "type": "Cricket/Player", "entity": { "fibery/id": "20f9b920-9752-11e9-81b9-4363f716f666", "icon/icon": ":smile:" } } } ' ``` ## How to work with the Lookup Field? A Lookup Field is basically the same as a Formula field. Feel free to share your use case in [the community](https://community.fibery.io/). ## How to work with Document View? This API is still undocumented. However, to work with Document View content using ordinary or api documents, you need only the document secret. To obtain that secret for a document view with public id "45" one may query views api as described in the [Views](/guides/http-api/views) guide. ```json theme={null} { "jsonrpc": "2.0", "method": "query-views", "params": { "filter": { "publicIds": [ "45" ] } } } ``` Response ```json theme={null} { "jsonrpc": "2.0", "result": [ { "fibery/id": "43addb30-1fd0-11ee-9009-a7c752e861c6", "fibery/public-id": "45", "fibery/name": "Supa Doc", "fibery/icon": null, "fibery/description": null, "fibery/rank": -9006999178042705, "fibery/type": "document", "fibery/meta": { "documentSecret": "e27df257-0e6f-441f-8dcc-fde2591d12c3" }, ... } ] } ``` See the `"fibery/meta"` property with `"documentSecret"` in it. With this UUID you may do whatever you need with document content via standard documents API. ## How can I check who has which permissions (capabilities) for a specific database? You can use the **`fibery.type/query-capability-sources`** API command.\ It returns all users who have access to a given database (type) and explains how each user obtained their permissions. ### Basic API Call ```json theme={null} [{ "command": "fibery.type/query-capability-sources", "args": { "type": "SoftDev/Task" } }] ``` This returns all active users by default along with their effective capabilities for the specified database. ### **Optional: Explicitly Limit to Active Users** ```json theme={null} [{ "command": "fibery.type/query-capability-sources", "args": { "type": "SoftDev/Task", "active-users?": true } }] ``` > ℹ️ Note: `active-users?` is optional β€” active users are returned by default. ## API Token Activity Details You can see the "Created" and "Last Used" dates for API tokens, along with the token prefix. For older tokens, the creation date won't be available and will show as "N/A." Activity tracking (last used date) will also be shown from September 19th, 2024. ## Is it possible to create a Formula field via API? At the moment, we don't have a public API for this specific functionality. That said, it is technically possible to observe how Fibery's UI interacts with the backend by inspecting network requests β€” and from there, infer the structure of the API calls. However, please note that these internal APIs aren't officially supported and may change at any time without notice, so we can't guarantee stability or backward compatibility. If you have a particular use case in mind, feel free to share it β€” we might be able to suggest a safer or more stable approach. ## Troubleshooting #### I'm facing timeouts when querying Entities Use paging api. # Fields Source: https://developers.fibery.com/guides/http-api/fields Learn how to create and manage Fields using the Fibery API. A Field is a part of a [Database](/guides/http-api/databases). Learn more about it in the [Schema guide](/guides/http-api/schema). The API uses `type` for Database and `app` for Space. See [Terminology](/guides/general/terminology#api-naming). ## Create Field ### Primitive Field ```javascript JavaScript theme={null} const response = await fetch('https://YOUR_ACCOUNT.fibery.io/api/commands', { method: 'POST', headers: { 'Authorization': 'Token YOUR_TOKEN', 'Content-Type': 'application/json' }, body: JSON.stringify({ command: 'fibery.schema/batch', args: { commands: [ { command: 'schema.field/create', args: { 'fibery/holder-type': 'Cricket/Player', 'fibery/name': 'Cricket/Salary', 'fibery/type': 'fibery/int', 'fibery/meta': { 'fibery/readonly?': false, 'fibery/default-value': 1000, 'ui/number-unit': 'USD' } } } ] } }) }); const data = await response.json(); ``` ```bash cURL theme={null} curl -X POST https://YOUR_ACCOUNT.fibery.io/api/commands \ -H 'Authorization: Token YOUR_TOKEN' \ -H 'Content-Type: application/json' \ -d ' { "command": "fibery.schema/batch", "args": { "commands": [ { "command": "schema.field/create", "args": { "fibery/holder-type": "Cricket/Player", "fibery/name": "Cricket/Salary", "fibery/type": "fibery/int", "fibery/meta": { "fibery/readonly?": false, "fibery/default-value": 1000, "ui/number-unit": "USD" } } } ] } } ' ``` Response: ```json theme={null} { "success": true, "result": "ok" } ``` Primitive Field types | Field type | Example | Comments | | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- | | `fibery/int` | `42` | | | `fibery/decimal` | `0.33` | | | `fibery/bool` | `true` | | | `fibery/text` | `Don't panic` | Up to 1k characters. Can be styled using `ui/type` meta flag: `text` \| `email` \| `phone` \| `url` | | `~~fibery/email~~` | [~~contact@megadodo.com~~](mailto:contact@megadodo.com) | Deprecated. Use a `fibery/text` field with `ui/type` meta set to `"email"`: `{"ui/type": "email"}` | | `fibery/emoji` | 🏏 | | | `fibery/date` | `1979-10-12` | | | `fibery/date-time` | `2019-06-24T12:25:20.812Z` | | | `fibery/date-range` | `{"start": "2019-06-27", "end": "2019-06-30"}` | | | `fibery/date-time-range` | `{"start": "2019-06-18T02:40:00.000Z", "end": "2019-07-25T11:40:00.000Z"}` | | | `fibery/location` | `{"longitude": 2.349606, "latitude": 48.890764, "fullAddress": "MΓ©tro Marcadet Poissonniers, 67 boulevard BarbΓ¨s, Paris, 75018, France", "addressParts": {"city": "Paris", "country": "France"}}` | All address parts are optional. | | `fibery/uuid` | `acb5ef80-9679-11e9-bc42-526af7764f64` | | | `fibery/rank` | `1000` | | | `fibery/json-value` | `{"paranoid?": true}` | | Command parameters | Parameter (required in bold) | Description | Example | | ------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | | **`fibery/holder-type`** | Holder Database name in `${space}/${name}` format | `Cricket/Player` | | **`fibery/name`** | Field name in `${space}/${name}` format. | `Cricket/Salary` | | **`fibery/type`** | One of the primitive Field types above or a Database for a one-way relation. | `fibery/int` | | meta.`fibery/readonly?` | If users are able to change value from UI | true | | meta.`fibery/default-value` | The value automatically set when a new entity is created | "(empty)" | | meta.`fibery/unique?` | Makes field values to be unique across the whole Database Only one of unique flags can be used at a time as `fibery/unique?` and `fibery/case-insensitive-text-unique?` are mutually exclusive | true | | meta.`fibery/case-insensitive-text-unique?` | makes field values to be unique case insensitive across whole Database | true | #### Create unique case insensitive Text Field: ```javascript JavaScript theme={null} const response = await fetch('https://YOUR_ACCOUNT.fibery.io/api/commands', { method: 'POST', headers: { 'Authorization': 'Token YOUR_TOKEN', 'Content-Type': 'application/json' }, body: JSON.stringify({ command: 'fibery.schema/batch', args: { commands: [ { command: 'schema.field/create', args: { 'fibery/holder-type': 'Cricket/Player', 'fibery/name': 'Cricket/SSN', 'fibery/type': 'fibery/text', 'fibery/meta': { 'fibery/readonly?': false, 'fibery/case-insensitive-text-unique?': true } } } ] } }) }); const data = await response.json(); ``` ```bash cURL theme={null} curl -X POST https://YOUR_ACCOUNT.fibery.io/api/commands \ -H 'Authorization: Token YOUR_TOKEN' \ -H 'Content-Type: application/json' \ -d ' { "command": "fibery.schema/batch", "args": { "commands": [ { "command": "schema.field/create", "args": { "fibery/holder-type": "Cricket/Player", "fibery/name": "Cricket/SSN", "fibery/type": "fibery/text", "fibery/meta": { "fibery/readonly?": false, "fibery/case-insensitive-text-unique?": true } } } ] } } ' ``` Response: ```json theme={null} { "success": true, "result": "ok" } ``` #### Create unique Int Field: ```javascript JavaScript theme={null} const response = await fetch('https://YOUR_ACCOUNT.fibery.io/api/commands', { method: 'POST', headers: { 'Authorization': 'Token YOUR_TOKEN', 'Content-Type': 'application/json' }, body: JSON.stringify({ command: 'fibery.schema/batch', args: { commands: [ { command: 'schema.field/create', args: { 'fibery/holder-type': 'Cricket/Player', 'fibery/name': 'Cricket/Jersey Number', 'fibery/type': 'fibery/int', 'fibery/meta': { 'fibery/readonly?': false, 'fibery/unique?': true } } } ] } }) }); const data = await response.json(); ``` ```bash cURL theme={null} curl -X POST https://YOUR_ACCOUNT.fibery.io/api/commands \ -H 'Authorization: Token YOUR_TOKEN' \ -H 'Content-Type: application/json' \ -d ' { "command": "fibery.schema/batch", "args": { "commands": [ { "command": "schema.field/create", "args": { "fibery/holder-type": "Cricket/Player", "fibery/name": "Cricket/Jersey Number", "fibery/type": "fibery/int", "fibery/meta": { "fibery/readonly?": false, "fibery/unique?": true } } } ] } } ' ``` Response: ```json theme={null} { "success": true, "result": "ok" } ``` ### Relation (entity \[collection] Field) To create a relation between two Databases, we create a pair of entity \[collection] Fields and connect them with a unique identifier. The relation is to-one by default. Set entity Field's meta.`fibery/collection?` to `true` for to-many relation. ```javascript JavaScript theme={null} const relationId = 'd9e9ec34-9685-11e9-8550-526af7764f64'; const response = await fetch('https://YOUR_ACCOUNT.fibery.io/api/commands', { method: 'POST', headers: { 'Authorization': 'Token YOUR_TOKEN', 'Content-Type': 'application/json' }, body: JSON.stringify({ command: 'fibery.schema/batch', args: { commands: [ { command: 'schema.field/create', args: { 'fibery/holder-type': 'Cricket/Player', 'fibery/name': 'Cricket/Current Team', 'fibery/type': 'Cricket/Team', 'fibery/meta': { 'fibery/relation': relationId } } }, { command: 'schema.field/create', args: { 'fibery/holder-type': 'Cricket/Team', 'fibery/name': 'Cricket/Current Roster', 'fibery/type': 'Cricket/Player', 'fibery/meta': { 'fibery/collection?': true, 'fibery/relation': relationId } } } ] } }) }); const data = await response.json(); ``` ```bash cURL theme={null} curl -X POST https://YOUR_ACCOUNT.fibery.io/api/commands \ -H 'Authorization: Token YOUR_TOKEN' \ -H 'Content-Type: application/json' \ -d ' { "command": "fibery.schema/batch", "args": { "commands": [ { "command": "schema.field/create", "args": { "fibery/holder-type": "Cricket/Player", "fibery/name": "Cricket/Current Team", "fibery/type": "Cricket/Team", "fibery/meta": { "fibery/relation": "d9e9ec34-9685-11e9-8550-526af7764f64" } } }, { "command": "schema.field/create", "args": { "fibery/holder-type": "Cricket/Team", "fibery/name": "Cricket/Current Roster", "fibery/type": "Cricket/Player", "fibery/meta": { "fibery/collection?": true, "fibery/relation": "d9e9ec34-9685-11e9-8550-526af7764f64" } } } ] } } ' ``` Response: ```json theme={null} { "success": true, "result": "ok" } ``` Command parameters | Parameter (required in bold) | Description | Example | | ---------------------------- | ----------------------------------------------------- | ---------------------- | | **`fibery/holder-type`** | Holder Database name in `${space}/${name}` format | `Cricket/Player` | | **`fibery/name`** | Field name in `${space}/${name}` format. | `Cricket/Current Team` | | **`fibery/type`** | Related Database name in `${space}/${name}` format | `Cricket/Team` | | meta.**`fibery/relation`** | UUID shared between the pair of Fields. | d9e9ec34-96... | | meta.`fibery/collection?` | `true` for to-many relation (entity collection Field) | true | | meta.`fibery/readonly?` | If users are able to change value from UI | true | ### Single-select Field A single-select Field is not what it seems to be. Actually, every single-select option is an Entity of a newly created special Database. This way unlocks 'name on UI + value in Formula' scenario (think `Self conviction` β†’ `0.01` in GIST) and enables an easy transition to a fully functional Database. To create a single-select Field we should: 1. Create a new `enum` Database 2. Create a Field of the newly created `enum` Database 3. Create an Entity for each single-select option 4. Make the selection required and set the default value The new `enum` Database name is built using this format: `${space}/${field}_${app}/${holder-type}`. ```javascript JavaScript theme={null} const enumType = 'Cricket/Batting Hand_Cricket/Player'; const rightId = '4a3ffb10-9747-11e9-9def-016e5ea5e162'; const leftId = '4a402220-9747-11e9-9def-016e5ea5e162'; const response = await fetch('https://YOUR_ACCOUNT.fibery.io/api/commands', { method: 'POST', headers: { 'Authorization': 'Token YOUR_TOKEN', 'Content-Type': 'application/json' }, body: JSON.stringify({ command: 'fibery.command/batch', args: { commands: [ { command: 'fibery.schema/batch', args: { commands: [ { command: 'schema.enum/create', args: { 'fibery/name': enumType } }, { command: 'schema.field/create', args: { 'fibery/holder-type': 'Cricket/Player', 'fibery/name': 'Cricket/Batting Hand', 'fibery/type': enumType } } ] } }, { command: 'fibery.entity/create', args: { type: enumType, entity: { 'enum/name': 'Right', 'fibery/id': rightId, 'fibery/rank': 0 } } }, { command: 'fibery.entity/create', args: { type: enumType, entity: { 'enum/name': 'Left', 'fibery/id': leftId, 'fibery/rank': 1000000 } } }, { command: 'fibery.schema/batch', args: { commands: [ { command: 'schema.field/set-meta', args: { name: 'Cricket/Batting Hand', 'holder-type': 'Cricket/Player', key: 'fibery/default-value', value: { 'fibery/id': rightId } } }, { command: 'schema.field/set-meta', args: { name: 'Cricket/Batting Hand', 'holder-type': 'Cricket/Player', key: 'fibery/required?', value: true } } ] } } ] } }) }); const data = await response.json(); ``` ```bash cURL theme={null} curl -X POST https://YOUR_ACCOUNT.fibery.io/api/commands \ -H 'Authorization: Token YOUR_TOKEN' \ -H 'Content-Type: application/json' \ -d ' { "command": "fibery.command/batch", "args": { "commands": [ { "command": "fibery.schema/batch", "args": { "commands": [ { "command": "schema.enum/create", "args": { "fibery/name": "Cricket/Batting Hand_Cricket/Player" } }, { "command": "schema.field/create", "args": { "fibery/holder-type": "Cricket/Player", "fibery/name": "Cricket/Batting Hand", "fibery/type": "Cricket/Batting Hand_Cricket/Player" } } ] } }, { "command": "fibery.entity/create", "args": { "type": "Cricket/Batting Hand_Cricket/Player", "entity": { "enum/name": "Right", "fibery/id": "4a3ffb10-9747-11e9-9def-016e5ea5e162", "fibery/rank": 0 } } }, { "command": "fibery.entity/create", "args": { "type": "Cricket/Batting Hand_Cricket/Player", "entity": { "enum/name": "Left", "fibery/id": "4a402220-9747-11e9-9def-016e5ea5e162", "fibery/rank": 1000000 } } }, { "command": "fibery.schema/batch", "args": { "commands": [ { "command": "schema.field/set-meta", "args": { "name": "Cricket/Batting Hand", "holder-type": "Cricket/Player", "key": "fibery/default-value", "value": { "fibery/id": "4a3ffb10-9747-11e9-9def-016e5ea5e162" } } }, { "command": "schema.field/set-meta", "args": { "name": "Cricket/Batting Hand", "holder-type": "Cricket/Player", "key": "fibery/required?", "value": true } } ] } } ] } } ' ``` Response: ```json theme={null} { "success": true, "result": [ { "success": true, "result": "ok" }, { "success": true, "result": { "fibery/id": "4a3ffb10-9747-11e9-9def-016e5ea5e162", "fibery/public-id": "1", "enum/name": "Right", "fibery/rank": 0 } }, { "success": true, "result": { "fibery/id": "4a402220-9747-11e9-9def-016e5ea5e162", "fibery/public-id": "2", "enum/name": "Left", "fibery/rank": 1000000 } }, { "success": true, "result": "ok" } ] } ``` ### Rich text Field In Fibery, every rich text Field instance is, in fact, a collaborative document. It means that for each Entity with N rich text Fields Fibery automatically creates N documents. Each of these documents is stored in Document Storage and is connected to its Entity through an auxiliary `Collaboration~Documents/Document` Entity: Entity --- (magic) ---> `Collab Doc/Document` --- (`fibery/secret`) ---> Document in Storage So to create a rich text Field we just connect our Database with the `Collaboration~Documents/Document` Database. This Database has a special property: the entities inside it inherit access from their Parent Entity. To indicate that Parent-Child relationship we pass `fibery/entity-component?` meta flag, but only for ordinary Fields (e.g. not Lookup and not Formula) Selecting and updating a rich text Field is a two-step process: 1. Get `fibery/secret` of the related Document. 2. Work with this Document via `api/documents` Storage endpoint. ```javascript JavaScript theme={null} const response = await fetch('https://YOUR_ACCOUNT.fibery.io/api/commands', { method: 'POST', headers: { 'Authorization': 'Token YOUR_TOKEN', 'Content-Type': 'application/json' }, body: JSON.stringify({ command: 'fibery.schema/batch', args: { commands: [ { command: 'schema.field/create', args: { 'fibery/holder-type': 'Cricket/Player', 'fibery/name': 'Cricket/Bio', 'fibery/type': 'Collaboration~Documents/Document', 'fibery/meta': { 'fibery/entity-component?': true } } } ] } }) }); const data = await response.json(); ``` ```bash cURL theme={null} curl -X POST https://YOUR_ACCOUNT.fibery.io/api/commands \ -H 'Authorization: Token YOUR_TOKEN' \ -H 'Content-Type: application/json' \ -d ' { "command": "fibery.schema/batch", "args": { "commands": [ { "command": "schema.field/create", "args": { "fibery/holder-type": "Cricket/Player", "fibery/name": "Cricket/Bio", "fibery/type": "Collaboration~Documents/Document", "fibery/meta": { "fibery/entity-component?": true } } } ] } } ' ``` Response: ```json theme={null} { "success": true, "result": "ok" } ``` ## Rename Field ```javascript JavaScript theme={null} const response = await fetch('https://YOUR_ACCOUNT.fibery.io/api/commands', { method: 'POST', headers: { 'Authorization': 'Token YOUR_TOKEN', 'Content-Type': 'application/json' }, body: JSON.stringify({ command: 'fibery.schema/batch', args: { commands: [ { command: 'schema.field/rename', args: { 'holder-type': 'Cricket/Player', 'from-name': 'Cricket/Position', 'to-name': 'Cricket/Role' } } ] } }) }); const data = await response.json(); ``` ```bash cURL theme={null} curl -X POST https://YOUR_ACCOUNT.fibery.io/api/commands \ -H 'Authorization: Token YOUR_TOKEN' \ -H 'Content-Type: application/json' \ -d ' { "command": "fibery.schema/batch", "args": { "commands": [ { "command": "schema.field/rename", "args": { "holder-type": "Cricket/Player", "from-name": "Cricket/Position", "to-name": "Cricket/Role" } } ] } } ' ``` Response: ```json theme={null} { "success": true, "result": "ok" } ``` Command parameters | Parameter (required in bold) | Description | Example | | ---------------------------- | ------------------------------------------------- | ------------------ | | **`holder-type`** | Holder Database name in `${space}/${name}` format | `Cricket/Player` | | **`from-name`** | Current Field name in `${space}/${name}` format | `Cricket/Position` | | **`to-name`** | New Field name in `${space}/${name}` format | `Cricket/Role` | ## Delete Field ```javascript JavaScript theme={null} const response = await fetch('https://YOUR_ACCOUNT.fibery.io/api/commands', { method: 'POST', headers: { 'Authorization': 'Token YOUR_TOKEN', 'Content-Type': 'application/json' }, body: JSON.stringify({ command: 'fibery.schema/batch', args: { commands: [ { command: 'schema.field/delete', args: { 'holder-type': 'Cricket/Player', name: 'Cricket/Role', 'delete-values?': true } } ] } }) }); const data = await response.json(); ``` ```bash cURL theme={null} curl -X POST https://YOUR_ACCOUNT.fibery.io/api/commands \ -H 'Authorization: Token YOUR_TOKEN' \ -H 'Content-Type: application/json' \ -d ' { "command": "fibery.schema/batch", "args": { "commands": [ { "command": "schema.field/delete", "args": { "holder-type": "Cricket/Player", "name": "Cricket/Role", "delete-values?": true } } ] } } ' ``` Response: ```json theme={null} { "success": true, "result": "ok" } ``` ### **Command parameters** | Parameter (required in bold) | Default | Description | Example | | ---------------------------- | ------- | ------------------------------------------------- | ---------------- | | **`holder-type`** | | Holder Database name in `${space}/${name}` format | `Cricket/Player` | | **`name`** | | Field name in `${space}/${name}` format | `Cricket/Role` | | **`delete-values?`** | false | See the behavior in the table below | true | To remove a relation, delete both entity \[collection] Fields within the same `fibery.schema/batch` command. `delete-values?` parameter behavior
**`delete-values?`**
**false** **true**
**Field type** **Empty [primitive](#primitive-field) Field** Field is deleted Field is deleted
**Non-empty primitive Field** Error is thrown Field and values are deleted
**Empty entity \[collection] Field** Field is deleted Field is deleted
**Non-empty entity \[collection] Field** Error is thrown Field and links (but not related Entities) are deleted
# Files Source: https://developers.fibery.com/guides/http-api/files Learn how to upload and manage files using the Fibery API. Working with Files is different from other scenarios. To upload or download a File, use `api/files` endpoint instead of the usual `api/commands`. When uploading a File, Fibery does two things: 1. Saves the File to Storage and gets File's `fibery/secret`. 2. Creates an Entity in the `fibery/file` Database with the `fibery/secret` from the previous step and gets `fibery/id`. When working with Storage (ex. downloading a File) use `fibery/secret`. For actions inside Fibery (ex. attaching a File to an Entity) use `fibery/id`: Parent Entity --- (`fibery/id`) ---> File Entity --- (`fibery/secret`) ---> File in Storage\ `fibery/file` Database | Field name | Field type | Example | | --------------------- | ------------- | ------------------------------------ | | `fibery/id` | `fibery/uuid` | c5bc1ec0-997e-11e9-bcec-8fb5f642f8a5 | | `fibery/secret` | `fibery/uuid` | c5815fb0-997e-11e9-bcec-8fb5f642f8a5 | | `fibery/name` | `fibery/text` | "vogon-ship.jpg" | | `fibery/content-type` | `fibery/text` | "image/jpeg" | ## Upload File Upload a locally stored File and get: * `fibery/id` to attach the File to an Entity; * `fibery/secret` to download the File. Upload an image from a Windows PC: ```javascript JavaScript theme={null} const fs = require('node:fs'); const file = new Blob( [fs.readFileSync('C:\\Users\\Trillian\\Pictures\\virat-kohli.jpg')], { type: 'image/jpeg' } ); const formData = new FormData(); formData.append('file', file, 'virat-kohli.jpg'); const response = await fetch('https://YOUR_ACCOUNT.fibery.io/api/files', { method: 'POST', headers: { 'Authorization': 'Token YOUR_TOKEN' }, body: formData }); const data = await response.json(); ``` ```bash cURL theme={null} curl -X POST https://YOUR_ACCOUNT.fibery.io/api/files \ -H 'Authorization: Token YOUR_TOKEN' \ -F 'file=@C:\Users\Trillian\Pictures\virat-kohli.jpg' ``` Result: ```json theme={null} { "fibery/id": "c5bc1ec0-997e-11e9-bcec-8fb5f642f8a5", "fibery/name": "virat-kohli.jpg", "fibery/content-type": "image/jpeg", "fibery/secret": "c5815fb0-997e-11e9-bcec-8fb5f642f8a5", "fibery/content-length": 312456, "fibery/rank": 0 } ``` ## Upload File from the web Upload a file from url and get: * `fibery/id` to attach the File to an Entity; * `fibery/secret` to download the File. | parameter | optional? | type | Example | | --------- | --------- | -------------- | -------------------------------------------------------------------------------------- | | `url` | required | `string` | "[https://example.com/files/attachment.pdf](https://example.com/files/attachment.pdf)" | | `name` | optional | `string` | "my file.pdf" | | `method` | optional | `GET\POST\...` | "POST" , defaults to "GET" | | `headers` | optional | `{}` | \{ "auth header": "auth key for 3rd party system" } | Upload an image from pixabay: ```javascript JavaScript theme={null} const response = await fetch('https://YOUR_ACCOUNT.fibery.io/api/files/from-url', { method: 'POST', headers: { 'Authorization': 'Token YOUR_TOKEN', 'Content-Type': 'application/json' }, body: JSON.stringify({ url: 'https://cdn.pixabay.com/photo/2016/03/28/10/05/kitten-1285341_1280.jpg', method: 'GET', name: 'img.jpg', headers: { 'auth header': 'auth key for url specified' } }) }); const data = await response.json(); ``` ```bash cURL theme={null} curl -X POST https://YOUR_ACCOUNT.fibery.io/api/files/from-url \ -H 'Authorization: Token YOUR_TOKEN' \ -H 'Content-Type: application/json' \ -d '{ "url": "https://cdn.pixabay.com/photo/2016/03/28/10/05/kitten-1285341_1280.jpg", "method": "GET", "name": "img.jpg", "headers": { "auth header": "auth key for url specified" } }' ``` Result: ```json theme={null} { "fibery/id": "d5bc1ec0-997e-11e9-bcec-8fb5f642f8a5", "fibery/name": "img.jpg", "fibery/content-type": "image/jpeg", "fibery/secret": "f5815fb0-997e-11e9-bcec-8fb5f642f8a5", "fibery/content-length": 158948, "fibery/rank": 0 } ``` ## **Download File** Download a File by providing `fibery/secret`. ```javascript JavaScript theme={null} const fs = require('node:fs'); const response = await fetch( 'https://YOUR_ACCOUNT.fibery.io/api/files/c5815fb0-997e-11e9-bcec-8fb5f642f8a5', { headers: { 'Authorization': 'Token YOUR_TOKEN' } } ); const buffer = Buffer.from(await response.arrayBuffer()); fs.writeFileSync('./virat.jpg', buffer); ``` ```bash cURL theme={null} curl -L -X GET https://YOUR_ACCOUNT.fibery.io/api/files/c5815fb0-997e-11e9-bcec-8fb5f642f8a5 \ -H 'Authorization: Token YOUR_TOKEN' \ -o ./virat.jpg ``` Note: there is no place on UI where you can find any arbitrary file. You can download it by URL above (or preview by browser if file format allows it).\ To view it file on UI one must place it in the Rich Text or Document or into Files extension for some Entity. ## Get temporary public File Url You can get a signed url which is valid for 60 minutes by calling `/sign-urls` | parameter | optional? | type | Example | | --------- | --------- | ---------- | ----------------------------------------- | | `secrets` | required | `string[]` | \["c5815fb0-997e-11e9-bcec-8fb5f642f8a5"] | ```javascript JavaScript theme={null} const response = await fetch('https://YOUR_ACCOUNT.fibery.io/api/files/sign-urls', { method: 'POST', headers: { 'Authorization': 'Token YOUR_TOKEN', 'Content-Type': 'application/json' }, body: JSON.stringify({ secrets: ['c5815fb0-997e-11e9-bcec-8fb5f642f8a5'] }) }); const data = await response.json(); ``` ```bash cURL theme={null} curl -X POST https://YOUR_ACCOUNT.fibery.io/api/files/sign-urls \ -H 'Authorization: Token YOUR_TOKEN' \ -H 'Content-Type: application/json' \ -d '{ "secrets": ["c5815fb0-997e-11e9-bcec-8fb5f642f8a5"] }' ``` Result: ```json theme={null} { "items": [ { "secret": "c5815fb0-997e-11e9-bcec-8fb5f642f8a5", "url": "https://....", "expiresAt": "2026-01-13T20:48:37.524Z" } ] } ``` ## **Attach File to Entity** Before attaching a File, make sure that you have a Files field added for the Entity's Database: Screenshot 2025-10-23 at 16.39.02.png Screenshot 2025-10-23 at 16.29.42.png Suppose you've added a Files field like it shown on image above. It creates Files Field as an entity collection Field called `/Files`. Attach and remove Files the same way you update any other entity collection Field. In case you disable `Allow multiple files` option the field is created as a single file field. In examples bellow we're considering that you're working with files collection, please consult your [Query entities](/guides/http-api/query-entities) guide to query single file field data. Attach a picture to a Player's profile: ```javascript JavaScript theme={null} const response = await fetch('https://YOUR_ACCOUNT.fibery.io/api/commands', { method: 'POST', headers: { 'Authorization': 'Token YOUR_TOKEN', 'Content-Type': 'application/json' }, body: JSON.stringify({ command: 'fibery.entity/add-collection-items', args: { type: 'Cricket/Player', field: 'Cricket/Photos', entity: { 'fibery/id': '20f9b920-9752-11e9-81b9-4363f716f666' }, items: [ { 'fibery/id': 'c5bc1ec0-997e-11e9-bcec-8fb5f642f8a5' } ] } }) }); const data = await response.json(); ``` ```bash cURL theme={null} curl -X POST https://YOUR_ACCOUNT.fibery.io/api/commands \ -H 'Authorization: Token YOUR_TOKEN' \ -H 'Content-Type: application/json' \ -d ' { "command": "fibery.entity/add-collection-items", "args": { "type": "Cricket/Player", "field": "Cricket/Photos", "entity": { "fibery/id": "20f9b920-9752-11e9-81b9-4363f716f666" }, "items": [ { "fibery/id": "c5bc1ec0-997e-11e9-bcec-8fb5f642f8a5" } ] } } ' ``` Response: ```json theme={null} { "success": true, "result": "ok" } ``` ## **Download attachments** To download Entity's attached Files: 1. Get the Files `fibery/secret`; 2. Download each File using `fibery/secret`. Get attached Files `fibery/secret` for a particular Player Entity: ```javascript JavaScript theme={null} const response = await fetch('https://YOUR_ACCOUNT.fibery.io/api/commands', { method: 'POST', headers: { 'Authorization': 'Token YOUR_TOKEN', 'Content-Type': 'application/json' }, body: JSON.stringify({ command: 'fibery.entity/query', args: { query: { 'q/from': 'Cricket/Player', 'q/select': [ 'fibery/id', { 'Cricket/Photos': { 'q/select': ['fibery/secret'], 'q/limit': 100 } } ], 'q/where': ['=', ['fibery/id'], '$entity-id'], 'q/limit': 1 }, params: { '$entity-id': '20f9b920-9752-11e9-81b9-4363f716f666' } } }) }); const data = await response.json(); ``` ```bash cURL theme={null} curl -X POST https://YOUR_ACCOUNT.fibery.io/api/commands \ -H 'Authorization: Token YOUR_TOKEN' \ -H 'Content-Type: application/json' \ -d ' { "command": "fibery.entity/query", "args": { "query": { "q/from": "Cricket/Player", "q/select": [ "fibery/id", { "Cricket/Photos": { "q/select": ["fibery/secret"], "q/limit": 100 } } ], "q/where": ["=", ["fibery/id"], "$entity-id"], "q/limit": 1 }, "params": {"$entity-id": "20f9b920-9752-11e9-81b9-4363f716f666"} } } ' ``` Grab the secrets: ```json theme={null} { "success": true, "result": [ { "fibery/id": "20f9b920-9752-11e9-81b9-4363f716f666", "Cricket/Photos": [ { "fibery/secret": "a71a7f30-9991-11e9-b8d4-8aba22381101" }, { "fibery/secret": "c5815fb0-997e-11e9-bcec-8fb5f642f8a5" } ] } ] } ``` Download Files using these secrets: ```javascript JavaScript theme={null} const fs = require('node:fs'); const secrets = [ 'a71a7f30-9991-11e9-b8d4-8aba22381101', 'c5815fb0-997e-11e9-bcec-8fb5f642f8a5' ]; await Promise.all(secrets.map(async (secret) => { const response = await fetch( `https://YOUR_ACCOUNT.fibery.io/api/files/${secret}`, { headers: { 'Authorization': 'Token YOUR_TOKEN' } } ); const buffer = Buffer.from(await response.arrayBuffer()); fs.writeFileSync(`./${secret}.bin`, buffer); })); ``` ```bash cURL theme={null} curl -L -X GET https://YOUR_ACCOUNT.fibery.io/api/files/a71a7f30-9991-11e9-b8d4-8aba22381101 \ -H 'Authorization: Token YOUR_TOKEN' \ -o ./a71a7f30.bin curl -L -X GET https://YOUR_ACCOUNT.fibery.io/api/files/c5815fb0-997e-11e9-bcec-8fb5f642f8a5 \ -H 'Authorization: Token YOUR_TOKEN' \ -o ./c5815fb0.bin ``` ### FAQ #### How to Upload Images via API From External Sources ```javascript theme={null} await fetch('/api/files/from-url', { method: "post", headers: { 'Content-Type': "application/json; charset=utf-8" }, body: JSON.stringify({url: "file-url", name: "file-name", id: "optional UUID of file being created"}), }); ``` # History Source: https://developers.fibery.com/guides/http-api/history Learn how to read Entity change history using the Fibery API. Use the History API to access a full, paginated log of every change made in your Fibery workspace. Common use cases include: * Building custom audit trails and compliance reports * Generating activity digests or changelogs ("what changed this week?") * Debugging automation behavior by inspecting what triggered a change * Powering external change-notification workflows This API does **not** include changes to Documents content (rich text fields). ## POST `/api/history/v2/search` Returns a paginated list of workspace history entries matching the given filters. ### Request body #### **Parameters** | Parameter | Required | Description | | ------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------- | | `where` | No | Array of filter objects. All filters are combined with logical `AND`. Pass `[]` for no filters. | | `timeframe` | No | Date range to search. Maximum span is **1 year**. If omitted, defaults to last 30 days. Values are ISO 8601 datetime strings in UTC. | | `limit` | Yes | Number of items per page. The API may return fewer items than requested even when more pages exist β€” always check `nextPage.hasNext`. | | `excludeAutomaticChanges` | No | Specifies which types of automatic changes to exclude from processing. | | `sinceItem` | No | ID of the last item from the previous page. Pass this to retrieve the next page. | #### **Sample request** ```json theme={null} { "where": [ { "field": "typeId", "operator": "=", "value": "{database-uuid}" }, { "field": "action", "operator": "in", "value": [ "fibery.entity/create", "fibery.entity/delete" ] } ], "timeframe": { "start": "{start-datetime}", "end": "{end-datetime}" }, "excludeAutomaticChanges": [ "automations", "integrations", "formulas", "auto-linking" ], "limit": 50, "sinceItem": "{since-item-id}" } ``` ### Filters (`where`) | Field | Description | Operators | Value | | ---------------- | ----------------------------------------------------------------------- | ---------------------------- | ------------------------------------------------------------------ | | `id` | History entry ID | `=` `in` | Number | | `action` | Kind of change performed | `=` `in` | See action values below | | `entityPublicId` | Entity short public ID | `=` `in` | String | | `entityState` | Current state of the entity | `=` `in` | `EXIST` `DELETED` `ARCHIVED` | | `author` | User who made the change | `=` `in` `empty` `not-empty` | User UUID. For `empty`/`not-empty`, omit the `value` key entirely. | | `typeId` | Database ID β€” returns entity changes only, no schema changes | `=` `in` | Database UUID | | `entityTypeId` | Database ID β€” returns entity changes **and** all related schema changes | `=` `in` | Database UUID | | `entityName` | Entity name (partial match) | `contains` | Text (minimum 3 characters) | | `entityId` | Entity UUID | `=` `in` | UUID | | `field` | Exclude specific field types from results | `not-in` | See field filter section below | #### **Available `action` values:** * `fibery.entity/create` * `fibery.entity/delete` * `fibery.entity/update` * `fibery.entity/add-collection-items` * `fibery.entity/remove-collection-items` * `history/fibery.entity/archived` * `history/fibery.entity/restored` * `history/permissions-changed` #### **The `field` filter (`not-in`):** Use this filter to exclude noisy system-generated changes and get a clean log of meaningful, human-made edits. It supports specific field names and wildcard aliases: | Alias | What it excludes | | ------------ | ------------------ | | `:hidden` | All hidden fields | | `:private` | All private fields | | `:deleted` | All deleted fields | | `:createdBy` | Created-by fields | | `:system` | All system fields | | `:name` | All title fields | Example β€” exclude hidden fields and rank fields: ```json theme={null} { "field": "field", "operator": "not-in", "value": [ { "field": ":hidden", "options": { "includeSoftDeleted": true } }, { "field": "fibery/rank" }, { "field": "fibery/menu-rank" }, { "type": "Collaboration~Documents/Document" }, { "type": "fibery/rich-text" }, { "field": "fibery/public-id" }, { "field": ":createdBy" } ] } ``` ### Exclude automatic changes (`excludeAutomaticChanges`) Use this option to exclude noisy changes made by system automatically | Alias | What it excludes | | -------------- | -------------------------------- | | `integrations` | All integration changes | | `automations` | Changes made by automation rules | | `auto-linking` | Automatically linked relations | | `formulas` | All formula fields | *** ### Response body #### **Example response** ```json theme={null} { "items": [ { "id": "120659039", "action": "fibery.entity/update", "date": "{ISO 8601 datetime}", "schemaVersion": 22782, "sequenceId": "18740668", "type": { "id": "1e1d96c0-5dcb-11e8-90b6-c6e140253257", "name": "Product Management/feature", "title": "Feature", "exist": true, "softDeleted": false }, "entity": { "id": "d7cea490-354d-11eb-9bfa-a53706ca0a40", "name": "History API", "exist": true, "softDeleted": false, "publicId": "2797", "capabilities": { "restore": false } }, "author": { "id": "79f2f29b-9a45-4e30-9ba6-b785f10c78ec", "name": "Eugene Kisel", "exist": true, "softDeleted": false, "publicId": "1210" }, "order": "120659039", "fromService": null, "caller": null, "changedValues": [ { "field": { "id": "56cba110-5dcb-11e8-90b6-c6e140253257", "name": "workflow/state", "title": "State" }, "currentValue": { "id": "5715c970-5dcb-11e8-90b6-c6e140253257", "name": "In Progress" }, "previousValue": { "id": "c9cfb7f0-a45d-11ec-b0b9-197891c72881", "name": "Next" } } ] } ], "nextPage": { "hasNext": true, "sinceItem": "120550073" } } ``` #### **`items` β€” array of history entries:** | Field | Description | | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | | `id` | Unique ID of this history entry | | `action` | The type of change that was made | | `date` | ISO 8601 datetime of when the change occurred | | `schemaVersion` | Workspace schema version at the time of the change β€” useful for correlating changes with schema migrations | | `sequenceId` | Monotonically increasing event sequence ID β€” use this to establish ordering when processing events across pages | | `type` | The Database the changed entity belongs to (`id`, `name`, `title`) | | `entity` | The entity that was changed (`id`, `name`, `publicId`, `exist`, `softDeleted`) | | `author` | The user who made the change. If `null`, the change was made by the system or an automation β€” see `fromService`. | | `fromService` | Internal Fibery service that triggered the change (set when `author` is `null`) | | `caller` | Additional source information related to `fromService` | | `changedValues` | List of field changes. Each item contains `field` (which field changed), `currentValue` (value after), and `previousValue` (value before). | #### **`nextPage` β€” pagination metadata:** | Field | Description | | ---------------- | ------------------------------------------------------------------------------------------------------------- | | `hasNext` | `true` if more results exist on the next page | | `sinceItem` | Pass this as `sinceItem` in the next request to retrieve the following page | | `hasNextLimited` | `true` when a workspace plan limit has been reached. Upgrade the workspace to access further history entries. | *** ## Datetime format All `timeframe` values must be ISO 8601 datetime strings in UTC. | Boundary | Example value | | ------------------------------- | -------------------------- | | Start of a day (midnight) | `2026-03-01T00:00:00.000Z` | | End of a day (last millisecond) | `2026-03-01T23:59:59.999Z` | | Start of an hour | `2026-03-01T14:00:00.000Z` | | Start of a minute | `2026-03-01T14:30:00.000Z` | The maximum allowed span between `start` and `end` is **1 year**. For the "Get all changes from a specific day" sample below: * Replace `{start-of-day}` with midnight on your chosen date β€” e.g. `2026-03-01T00:00:00.000Z` * Replace `{end-of-day}` with the last millisecond of that day β€” e.g. `2026-03-01T23:59:59.999Z` For all other samples, replace `{start-datetime}` and `{end-datetime}` with ISO 8601 values matching your desired range. ## Query samples ### Get all changes from a specific day ```json theme={null} { "where": [], "timeframe": { "start": "{start-of-day}", "end": "{end-of-day}" }, "limit": 50 } ``` ### Get changes to a specific Database Replace `{feature-type-uuid}` with the Database UUID (find it in Database settings). ```json theme={null} { "where": [ { "field": "typeId", "operator": "=", "value": "{feature-type-uuid}" } ], "timeframe": { "start": "{start-datetime}", "end": "{end-datetime}" }, "limit": 50, "sinceItem": "{next-page-since-item}" } ``` To include schema changes (field additions, renames, deletions) alongside entity changes, replace `typeId` with `entityTypeId`. ### Get all changes by a specific user ```json theme={null} { "where": [ { "field": "author", "operator": "=", "value": "{user-uuid}" } ], "timeframe": { "start": "{start-datetime}", "end": "{end-datetime}" }, "limit": 50, "sinceItem": "{next-page-since-item}" } ``` ### Get all changes to a specific entity By entity UUID: ```json theme={null} { "where": [ { "field": "typeId", "operator": "=", "value": "{type-uuid}" }, { "field": "entityId", "operator": "=", "value": "{entity-uuid}" } ], "timeframe": { "start": "{start-datetime}", "end": "{end-datetime}" }, "limit": 50 } ``` By public ID (the short numeric ID shown in the Fibery UI): ```json theme={null} { "where": [ { "field": "typeId", "operator": "=", "value": "{type-uuid}" }, { "field": "entityPublicId", "operator": "=", "value": "{entity-public-id}" } ], "timeframe": { "start": "{start-datetime}", "end": "{end-datetime}" }, "limit": 50 } ``` ### Exclude automatic and system-generated changes The log includes many system-generated changes by default (rank updates, formula recalculations, automation triggers). Use `field not-in` to filter them out: ```json theme={null} { "where": [ { "field": "field", "operator": "not-in", "value": [ { "field": ":hidden", "options": { "includeSoftDeleted": true } }, { "field": "fibery/rank" }, { "field": "fibery/menu-rank" }, { "type": "Collaboration~Documents/Document" }, { "type": "fibery/rich-text" }, { "field": "fibery/public-id" }, { "field": ":createdBy" }, ] } ], "excludeAutomaticChanges": ["integrations", "automations", "auto-linking", "formulas"], "timeframe": { "start": "{start-datetime}", "end": "{end-datetime}" }, "limit": 50 } ``` ### Paginating through results The API uses cursor-based pagination via `sinceItem`. To retrieve all pages: 1. Make an initial request without `sinceItem`. 2. If `nextPage.hasNext` is `true`, copy the value of `nextPage.sinceItem`. 3. Pass it as `sinceItem` in the next request. 4. Repeat until `hasNext` is `false`. The API may return fewer items per page than your `limit` value, even when `hasNext` is `true`. This can happen when long-running internal tasks are interrupted mid-page. Always use `hasNext` to determine whether more results exist β€” do not assume the last page has been reached just because a partial result was returned. ## Use cases Feel free to use the scripts below as is, fork them, or simply feed them to your coding agent as an example when working with History API. ### Find maximum historical value of a Number Field For each entity in a Database, get the maximum historical value of a certain Number Field. For example, understand what was the peak revenue for each customer. Here's the solution overview (typed by a human): 1. Filter entities to avoid querying everything in a Database (History API is quite slow by design). 2. For each entity, find the Field’s maximum historical value. 3. Write the results into a CSV file for manual inspection. 4. Take the CSV file and update another Field with the maximum historical value for each entity. And here's the source code (co-created with AI): [https://gitlab.com/fibery-community/api-examples/-/tree/master/extract-historical-max-value](https://gitlab.com/fibery-community/api-examples/-/tree/master/extract-historical-max-value) ### Find when a Number Field value crossed a threshold For each entity in a Database, understand when the value of a certain Number Field crossed a threshold. For example, for each Event, understand when the number of registrations crossed the 100 people mark β€” the point when an Event typically breaks even and is worth organizing. Here's the solution: 1. Find threshold crossings (e.g., when Subscription.Users became β‰₯ 2). 2. Enrich them (if the Number Field is on Subscriptions DB but the Date Field should be on Workspaces DB, we have to to map IDs). 3. Set the dates (β†’ Workspace.\[Second Purchase Date]). Source code in our community repo: [https://gitlab.com/fibery-community/api-examples/-/tree/master/find-threshold-crossings-via-history](https://gitlab.com/fibery-community/api-examples/-/tree/master/find-threshold-crossings-via-history) ## Related guides * [Activity Log](https://the.fibery.io/@public/User_Guide/Guide/Activity-Log-276) * [API overview](/guides/http-api/overview) # Overview Source: https://developers.fibery.com/guides/http-api/overview Get started with the Fibery HTTP API. The Fibery HTTP API lets you integrate Fibery with external systems and automate routine tasks. The API covers the following domains: Manage databases, fields, and workspace structure. Create, read, update, and delete data records. Create and configure saved views programmatically. Upload and attach files to entities. Subscribe to entity changes in real-time. Access a paginated log of every workspace change. Examples in this guide run against a `Cricket` Space. Install the [Cricket template](https://shared.fibery.io/t/ad2c7ce2-0d9c-4ce9-941f-5e8507a46e13-cricket) into your own workspace to run every example as-is and inspect the real responses. ## Commands API Every read or write is a POST to `/api/commands` with a command name and arguments. The endpoint works with batches of commands. The request should contain an array of commands with their names and arguments. You'll get an array back too. Take a look at the example below in which we retrieve the basic info about a user. ```javascript JavaScript theme={null} const response = await fetch('https://YOUR_ACCOUNT.fibery.io/api/commands', { method: 'POST', headers: { 'Authorization': 'Token YOUR_TOKEN', 'Content-Type': 'application/json' }, body: JSON.stringify({ command: 'fibery.entity/query', args: { query: { 'q/from': 'fibery/user', 'q/select': ['fibery/id', 'user/name'], 'q/limit': 1 } } }) }); const data = await response.json(); ``` ```bash cURL theme={null} curl -X POST https://YOUR_ACCOUNT.fibery.io/api/commands \ -H 'Authorization: Token YOUR_TOKEN' \ -H 'Content-Type: application/json' \ -d ' { "command": "fibery.entity/query", "args": { "query": { "q/from": "fibery/user", "q/select": ["fibery/id", "user/name"], "q/limit": 1 } } } ' ``` Here's the result: ```json theme={null} { "success": true, "result": [ { "fibery/id": "7dcf4730-82d2-11e9-8a28-82a9c787ee9d", "user/name": "Arthur Dent" } ] } ``` For the response envelope format and error handling, see [Response envelope](/api-reference/response-envelope). This guide uses **Database** and **Space** (the current UI vocabulary). API command names and JSON payloads still use the original `type` and `app` β€” see [Terminology](/guides/general/terminology#api-naming) for the mapping. # Pagination Source: https://developers.fibery.com/guides/http-api/pagination Learn how to paginate through large entity result sets. ## Overview `q/limit` caps how many Entities one query returns. To read a dataset larger than your page size, paginate with a cursor on `fibery/id`. ## Pagination pattern 1. Order results with `q/order-by: [[["fibery/id"], "q/asc"]]`. 2. Set `q/limit` to your page size + 1. The extra Entity is a sentinel β€” its presence in the response means another page exists. 3. On every page after the first, filter with `q/where: [">", ["fibery/id"], "$last-seen-id"]` and pass the previous page's last `fibery/id` in `params`. 4. Stop when a response contains `pageSize` Entities or fewer. If your query already has a `q/where`, combine it with the cursor filter using `["q/and", , [">", ["fibery/id"], "$last-seen-id"]]`. ## Example: page through a database The first page has no cursor β€” order by `fibery/id` and request one more than the page size: ```javascript JavaScript theme={null} const response = await fetch('https://YOUR_ACCOUNT.fibery.io/api/commands', { method: 'POST', headers: { 'Authorization': 'Token YOUR_TOKEN', 'Content-Type': 'application/json' }, body: JSON.stringify({ command: 'fibery.entity/query', args: { query: { 'q/from': 'Cricket/Player', 'q/select': ['fibery/id', 'Cricket/Name'], 'q/order-by': [[['fibery/id'], 'q/asc']], 'q/limit': 1001 } } }) }); const data = await response.json(); ``` ```bash cURL theme={null} curl -X POST https://YOUR_ACCOUNT.fibery.io/api/commands \ -H 'Authorization: Token YOUR_TOKEN' \ -H 'Content-Type: application/json' \ -d ' { "command": "fibery.entity/query", "args": { "query": { "q/from": "Cricket/Player", "q/select": ["fibery/id", "Cricket/Name"], "q/order-by": [[["fibery/id"], "q/asc"]], "q/limit": 1001 } } } ' ``` If the response contains more than 1000 Entities, drop the last one, take the `fibery/id` of the 1000th, and request the next page using it as a cursor: ```javascript JavaScript theme={null} const response = await fetch('https://YOUR_ACCOUNT.fibery.io/api/commands', { method: 'POST', headers: { 'Authorization': 'Token YOUR_TOKEN', 'Content-Type': 'application/json' }, body: JSON.stringify({ command: 'fibery.entity/query', args: { query: { 'q/from': 'Cricket/Player', 'q/select': ['fibery/id', 'Cricket/Name'], 'q/where': ['>', ['fibery/id'], '$last-seen-id'], 'q/order-by': [[['fibery/id'], 'q/asc']], 'q/limit': 1001 }, params: { '$last-seen-id': '21e578b0-9752-11e9-81b9-4363f716f666' } } }) }); const data = await response.json(); ``` ```bash cURL theme={null} curl -X POST https://YOUR_ACCOUNT.fibery.io/api/commands \ -H 'Authorization: Token YOUR_TOKEN' \ -H 'Content-Type: application/json' \ -d ' { "command": "fibery.entity/query", "args": { "query": { "q/from": "Cricket/Player", "q/select": ["fibery/id", "Cricket/Name"], "q/where": [">", ["fibery/id"], "$last-seen-id"], "q/order-by": [[["fibery/id"], "q/asc"]], "q/limit": 1001 }, "params": { "$last-seen-id": "21e578b0-9752-11e9-81b9-4363f716f666" } } } ' ``` A reusable helper that wraps the loop: ```javascript JavaScript theme={null} const PAGE_SIZE = 1000; const QUERY_LIMIT = PAGE_SIZE + 1; async function queryEntitiesPaginated({ query, params }) { const allEntities = []; let lastSeenId = null; let hasMorePages = true; while (hasMorePages) { const paginatedQuery = { ...query, 'q/order-by': [[['fibery/id'], 'q/asc']], 'q/limit': QUERY_LIMIT, }; if (lastSeenId) { const existingWhere = query['q/where']; paginatedQuery['q/where'] = existingWhere ? ['q/and', existingWhere, ['>', ['fibery/id'], '$last-seen-id']] : ['>', ['fibery/id'], '$last-seen-id']; } const paginatedParams = lastSeenId ? { ...params, '$last-seen-id': lastSeenId } : params; const response = await fetch('https://YOUR_ACCOUNT.fibery.io/api/commands', { method: 'POST', headers: { 'Authorization': 'Token YOUR_TOKEN', 'Content-Type': 'application/json', }, body: JSON.stringify({ command: 'fibery.entity/query', args: { query: paginatedQuery, params: paginatedParams }, }), }); const data = await response.json(); const entities = data.result; if (entities.length > PAGE_SIZE) { const pageEntities = entities.slice(0, PAGE_SIZE); allEntities.push(...pageEntities); lastSeenId = pageEntities[pageEntities.length - 1]['fibery/id']; } else { allEntities.push(...entities); hasMorePages = false; } } return allEntities; } ``` ## Loading collections and nested graphs Collection sub-queries can't be paginated directly. In every sub-query, set `q/limit: 100` and treat any collection that comes back at exactly the limit as potentially truncated. To get the full collection for those parents, query the child Database directly with the cursor pattern, scoped to one parent at a time. Step 1 β€” page through parents, selecting each parent's collection with `q/limit: 100`: ```javascript JavaScript theme={null} const response = await fetch('https://YOUR_ACCOUNT.fibery.io/api/commands', { method: 'POST', headers: { 'Authorization': 'Token YOUR_TOKEN', 'Content-Type': 'application/json' }, body: JSON.stringify({ command: 'fibery.entity/query', args: { query: { 'q/from': 'Cricket/Team', 'q/select': [ 'fibery/id', 'Cricket/Name', { 'Cricket/Current Players': { 'q/select': ['fibery/id', 'Cricket/Name'], 'q/limit': 100 } } ], 'q/order-by': [[['fibery/id'], 'q/asc']], 'q/limit': 1001 } } }) }); const data = await response.json(); ``` ```bash cURL theme={null} curl -X POST https://YOUR_ACCOUNT.fibery.io/api/commands \ -H 'Authorization: Token YOUR_TOKEN' \ -H 'Content-Type: application/json' \ -d ' { "command": "fibery.entity/query", "args": { "query": { "q/from": "Cricket/Team", "q/select": [ "fibery/id", "Cricket/Name", { "Cricket/Current Players": { "q/select": ["fibery/id", "Cricket/Name"], "q/limit": 100 } } ], "q/order-by": [[["fibery/id"], "q/asc"]], "q/limit": 1001 } } } ' ``` Step 2 β€” for any parent whose collection came back at 100 items, paginate the child Database directly. Filter by the back-reference Field that points from the child to the parent (here, `Cricket/Current Team` on `Cricket/Player`), combined with the `fibery/id` cursor: ```javascript JavaScript theme={null} const response = await fetch('https://YOUR_ACCOUNT.fibery.io/api/commands', { method: 'POST', headers: { 'Authorization': 'Token YOUR_TOKEN', 'Content-Type': 'application/json' }, body: JSON.stringify({ command: 'fibery.entity/query', args: { query: { 'q/from': 'Cricket/Player', 'q/select': ['fibery/id', 'Cricket/Name'], 'q/where': [ 'q/and', ['=', ['Cricket/Current Team', 'fibery/id'], '$team-id'], ['>', ['fibery/id'], '$last-seen-id'] ], 'q/order-by': [[['fibery/id'], 'q/asc']], 'q/limit': 1001 }, params: { '$team-id': '21e578b0-9752-11e9-81b9-4363f716f666', '$last-seen-id': '216c2a00-9752-11e9-81b9-4363f716f666' } } }) }); const data = await response.json(); ``` ```bash cURL theme={null} curl -X POST https://YOUR_ACCOUNT.fibery.io/api/commands \ -H 'Authorization: Token YOUR_TOKEN' \ -H 'Content-Type: application/json' \ -d ' { "command": "fibery.entity/query", "args": { "query": { "q/from": "Cricket/Player", "q/select": ["fibery/id", "Cricket/Name"], "q/where": [ "q/and", ["=", ["Cricket/Current Team", "fibery/id"], "$team-id"], [">", ["fibery/id"], "$last-seen-id"] ], "q/order-by": [[["fibery/id"], "q/asc"]], "q/limit": 1001 }, "params": { "$team-id": "21e578b0-9752-11e9-81b9-4363f716f666", "$last-seen-id": "216c2a00-9752-11e9-81b9-4363f716f666" } } } ' ``` The same approach extends to deeper graphs: page through the top-level Entities, then for each one page through its children, then for each child page through its grandchildren, and so on. This pattern assumes the child Database has a single Field pointing back to the parent (a one-to-many relation). For many-to-many collections, filter the child Database by the inverse collection Field with `q/in`, passing parent ids as a list β€” for example `"q/where": ["q/in", ["Cricket/Former Players", "fibery/id"], "$player-ids"]`. Combine with the `fibery/id` cursor under `q/and` as in Step 2. ## Avoid "q/no-limit" `"q/no-limit"` returns every matching Entity in a single response. On large Databases the request will time out. Use bounded limits and the pagination pattern above. **Top-level queries.** `"q/no-limit"` is supported but discouraged. Use `q/limit: 1000` and the cursor pattern. **Collection sub-queries.** `"q/no-limit"` is allowed today but planned for deprecation. Use `q/limit: 100` in sub-queries; if you need the full collection, paginate the child Database separately as shown in [Loading collections and nested graphs](#loading-collections-and-nested-graphs). # Query entities Source: https://developers.fibery.com/guides/http-api/query-entities Learn how to read Entities with select, filter, and order using the Fibery API. The API uses `type` for Database and `app` for Space. See [Terminology](/guides/general/terminology#api-naming). Install the [Cricket template](https://shared.fibery.io/t/ad2c7ce2-0d9c-4ce9-941f-5e8507a46e13-cricket) into your own workspace to run every example in the Entities section as-is. ## Overview The general shape of an entity query is: ```plaintext theme={null} { "q/from": // "fibery/user", "Kanban/Story" "q/select": | | `title` | `text` | Display name | | | `optional` | `boolean` | Indicates that user may leave Filter unset | | | `type` | `text` | Filter type (a list of supported Filter types and their customizations can be found below) | | | `secured` | `boolean` | Secured Filter values are not available for change by non-owner | | | `defaultValue` | `unknown` | Filter default value | | ### Filter configuration sample ```json theme={null} [ { "id": "channels", "title": "Channels", "datalist": true, "optional": false, "secured": true, "type": "multidropdown" }, { "id": "oldest", "title": "Oldest Message", "optional": false, "type": "datebox", "datalist": false }, { "id": "excludeAppMessages", "title": "Filter out APP messages", "optional": true, "type": "bool", "defaultValue": false } ] ``` ## Text Filter Simple text Filter available with `type="text"` : Screenshot 2024-01-31 at 16.08.06.png ### Configuration sample ```json theme={null} { "id": "text", "title": "Text", "type": "text" } ``` ## Number Filter Simple number Filter available with `type="number"` : Screenshot 2024-01-31 at 16.09.11.png ### Configuration sample ```json theme={null} { "id": "number", "title": "Number", "type": "number" } ``` ## Single Date Filter Simple single date Filter available with `type="datebox"`: Screenshot 2024-01-31 at 15.46.05.png ### Configuration sample ```json theme={null} { "id": "oldest", "title": "Oldest Message", "optional": false, "type": "datebox" } ``` ## Checkbox Filter Simple `true`/`false` Filter available with `type="bool"`: Screenshot 2024-01-31 at 15.50.46.png ### Configuration sample ```json theme={null} { "id": "excludeAppMessages", "title": "Filter out APP messages", "optional": true, "type": "bool" } ``` ## Single Select Filter Filter based on predefined values which is rendered as Single Select. Connector should provide available values via [datalist endpoint](/guides/integrations/rest-endpoints#post-/api/v1/synchronizer/datalist). Following attributes should be specified: * `type="list"` * `datalist=true` Screenshot 2024-01-31 at 16.00.19.png ### Configuration sample 1 ```json theme={null} { "id": "group", "title": "Group", "datalist": true, "type": "list" } ``` Also, it's possible to configure this Filter as a datalist dependent from another datalist (e.g., user should first select organization and only afterwards repository). It can be achieved by specifying `datalist_requires` attribute where value is an array of ID of another `datalists` . ### Configuration sample 2 ```json theme={null} [ { "id": "group", "title": "Group", "datalist": true, "type": "list" }, { "id": "project", "title": "Project", "datalist": true, "type": "list", "datalist_requires": [ "group" ] } ] ``` ## Multi Select Filter This Filter is based on predefined values which are rendered as Multi Select Field. The connector should provide available values via [datalist endpoint](/guides/integrations/rest-endpoints#post-/api/v1/synchronizer/datalist). Following attributes should be specified: * `type="multidropdown"` * `datalist=true` Screenshot 2024-01-31 at 16.03.54.png ### Configuration sample 1 ```json theme={null} { "id": "groups", "title": "Groups", "datalist": true, "type": "multidropdown" } ``` Also, it's possible to configure this Filter as a datalist dependent from another datalist (e.g., user should first select organization and only afterwards repository). It can be achieved by specifying `datalist_requires` attribute where value is an array of ID of another `datalists` ### Configuration sample 2 ```json theme={null} [ { "id": "group", "title": "Group", "datalist": true, "type": "list" }, { "id": "projects", "title": "Projects", "datalist": true, "type": "multidropdown", "datalist_requires": [ "group" ] } ] ``` If you haven't found applicable Filter, please contact us in the support chat or in [the community](https://community.fibery.io/ "https://community.fibery.io/"). # OAuth Source: https://developers.fibery.com/guides/integrations/oauth Implement OAuth authentication for a custom integration app. if your app provides OAuth capabilities for authentication, the authentication identifiers must be `oauth` and `oauth2` for OAuth v1 and OAuth v2, respectively. Only one authentication type per OAuth version is currently supported. ## **OAuth v1** ### **POST /oauth1/v1/authorize** The `POST /oauth1/v1/authorize` endpoint performs obtaining request token and secret and generating of authorization url for OAuth version 1 accounts. Included with the request is a single body parameter, `callback_uri`, which is the redirect URL that the user should be expected to be redirected to upon successful authentication with the third-party service. `callback_uri` includes query parameter `state` that MUST be preserved to be able to complete OAuth flow by Fibery. Request body sample: ```json theme={null} { "callback_uri": "https://oauth-svc.fibery.io/callback?state=xxxxxxx" } ``` Return body should include a `redirect_uri` that the user should be forwarded to in order to complete setup, `token` and `secret` are granted request token and secret by third-party service. Replies are then POST'ed to `/oauth1/v1/access_token` endpoint. The OAuth implementation requires the account identifier to be `oauth` for OAuth version 1. If service provider has callback url whitelisting than `https://oauth-svc.fibery.io?state=xxxxx` has to be added to the whitelist. Response body sample: ```json theme={null} { "redirect_uri": "https://trello.com/1/OAuthAuthorizeToken?oauth_token=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&name=TrelloIntegration", "token": "xxxx", "secret": "xxxx" } ``` ### **POST /oauth1/v1/access\_token** The `POST /oauth1/v1/access_token` endpoint performs the final setup and validation of OAuth version 1 accounts. Information as received from the third party upon redirection to the previously posted `callback_uri` are sent to this endpoint, with other applicable account information, for final setup. The account is then validated and, if successful, the account is returned; if there is an error, it is to be raised appropriately. The information that is sent to endpoint includes: * `fields.access_token` - request token granted during authorization step * `fields.access_secret` - request secret granted during authorization step * `fields.callback_uri` - callback uri that is used for user redirection * `oauth_verifier` - the verification code received upon accepting on third-party service consent screen. Request body sample: ```json theme={null} { "fields": { "access_token": "xxxx", // token value from authorize step "access_secret": "xxxxx", // secret value from authorize step "callback_uri": "https://oauth-svc.fibery.io?state=xxxxx" }, "oauth_verifier": "xxxxx" } ``` Response can include any data that will be used to authenticate account and fetch information. Tip: You can include parameters with `refresh_token` and `expires_on` and then on [validate step](/guides/integrations/rest-endpoints#post-/validate) proceed with access token refresh if it is expired or about to expire. Response body sample: ```json theme={null} { "access_token": "xxxxxx", "refresh_token": "xxxxxx", "expires_on": "2020-01-01T09:53:41.000Z" } ``` ## **OAuth v2** ### **POST /oauth2/v1/authorize** The `POST /oauth2/v1/authorize` endpoint performs the initial setup for OAuth version 2 accounts using `Authorization Code` grant type by generating `redirect_uri` based on received parameters. Request body includes following parameters: * `callback_uri` - is the redirect URL that the user should be expected to be redirected to upon successful authentication with the third-party service * `state` - opaque value used by the client to maintain state between request and callback. This value should be included in `redirect_uri` to be able to complete OAuth flow by Fibery. Request sample ```json theme={null} { "callback_uri": "https://oauth-svc.fibery.io", "state": "xxxxxx" } ``` Return body should include a `redirect_uri` that the user should be forwarded to in order to complete setup.\ Replies are then POST'ed to `/oauth2/v1/access_token` endpoint. The OAuth implementation requires the account identifier to be `oauth2` for OAuth version 2. If service provider has callback url whitelisting than `https://oauth-svc.fibery.io` has to be added to the whitelist. Response example: ```json theme={null} { "redirect_uri": "https://accounts.google.com/o/oauth2/token?state=xxxx&scope=openid+profile+email&client_secret=xxxx&grant_type=authorization_code&redirect_uri=something&code=xxxxx&client_id=xxxxx" } ``` ### **POST /oauth2/v1/access\_token** The `POST /oauth2/v1/access_token` endpoint performs the final setup and validation of OAuth version 2 accounts. Information as received from the third party upon redirection to the previously posted `callback_uri` are sent to this endpoint, with other applicable account information, for final setup. The account is then validated and, if successful, the account is returned; if there is an error, it is to be raised appropriately. The information that is sent to endpoint includes: * `fields.callback_uri` - callback uri that is used for user redirection * `code` - the authorization code received from the authorization server during redirect on `callback_uri` Request body sample: ```json theme={null} { "fields": { "callback_uri": "https://oauth-svc.fibery.io" }, "code": "xxxxx" } ``` Response can include any data that will be used to authenticate account and fetch information. Tip: You can include parameters with `refresh_token` and `expires_on` and then on [validate step](/guides/integrations/rest-endpoints#post-/validate) proceed with access token refresh if it is expired or about to expire. Response body sample: ```json theme={null} { "access_token": "xxxxxx", "refresh_token": "xxxxxx", "expires_on": "2020-01-01T09:53:41.000Z" } ``` # Overview Source: https://developers.fibery.com/guides/integrations/overview Build custom integration apps that sync data into Fibery. Integrations in Fibery are quite unusual. It replicates a part of an external app domain and feed data into Fibery and create several Databases. Dedicated service (integration application) should be implemented to configure and fetch data from an external application. ## How it works All communication between integration application and Fibery services is done via standard hypertext protocols, whether it can be HTTP or HTTPS. All integration applications are expected to adhere to a particular API format as outlined in this documentation. The underlying technologies used to develop these integration applications are up to the individual developer. Users may register their applications with Fibery by providing a HTTP or HTTPS URI for their service. The service must be accessible from the internet in order for the applications gallery to successfully communicate with the service. It is highly recommended that service providers consider utilizing HTTPS for all endpoints and limit access to these services only to IP addresses known to be from Fibery. In essence, Fibery's applications gallery service acts as a proxy between other Fibery services and the third party provider with some added logic for account and filter storage and validation. To understand more about our approach, please check the blog post about [Fibery approach to integration](https://fibery.io/blog/product-updates/fibery-approach-to-integration/). ## Availability for users Installed application will be available for all users in your Fibery workspace that have either Admin role in workspace, or Architect access in the Space where the application was configured. Users from another workspace won't be able to see or use your integration application. Once you sync data with this application, you can apply standard Fibery permissions on it. ## Dig deeper * [Tutorial: Holidays app](/guides/integrations/tutorial-holidays-app) * [Custom App: Fields](/guides/integrations/fields) * [Custom App: REST Endpoints](/guides/integrations/rest-endpoints) * [Custom App: OAuth](/guides/integrations/oauth) * [Custom App: Date range grammar](/guides/integrations/date-range-grammar) * [Custom App: Test and debug](/guides/integrations/test-and-debug) * [Integration Schema types](/guides/integrations/schema-types) * [Integration Filters](/guides/integrations/filters) * [Custom App: External actions API (beta)](/guides/integrations/external-actions-api) * [Custom App: Webhooks (beta)](/guides/integrations/webhooks) ### Tutorials * [Tutorial: Simple app](/guides/integrations/tutorial-simple-app) * [Tutorial: Notion sync](/guides/integrations/tutorial-notion-sync) ## FAQ ### If my access is revoked, will the Fibery integration stop working? Yes. The integration relies on the OAuth token of the user who originally set it up. * If your account is deactivated or your permissions are lowered so you no longer have API access, the token becomes invalid. * Fibery will no longer be able to "authenticate," and the sync will stop. ### Will my previously synced data be deleted? It depends on who restores the connection. Fibery follows a "safety-first" approach where records stay in the system, but "Deleted in App" status is sensitive to the permissions of the new Integration Owner. 1. If the original user restores the connection, a full sync runs and all data remains intact. 2. If a different user (e.g., Jane instead of John) reconnects the integration, Fibery respects the new user's permissions in the source app: * If the new owner has less access than the original owner, any records they cannot "see" in the source app will either disappear from the view or be marked as `Deleted in App = Yes`. * Records accessible to the new owner will stay synced and continue to receive updates. ### How can I prevent the integration from breaking? To ensure a permanent, stable sync, we recommend one of the following: * Set up the integration using a generic "system" or "service" user (e.g., `fibery-sync@yourcompany.com`) with high-level administrative access. This account is unaffected by individual staff turnover. * Before an admin leaves, a permanent Admin with identical or greater permissions should go to the Integration settings in Fibery and click "Reconnect" or "Update Credentials." > When changing the integration owner, always ensure the new owner has a "Super Admin" or equivalent role in the source app to prevent records from being marked as "Deleted" due to permission gaps. # REST endpoints Source: https://developers.fibery.com/guides/integrations/rest-endpoints HTTP endpoints a custom integration app must implement. Below are a list of the HTTP endpoints expected in an integration application. **Required** * `GET /`: returns app information * `POST /validate`: performs validation of the account * `POST /api/v1/synchronizer/config`: returns synchronizer configuration * `POST /api/v1/synchronizer/schema`: returns synchronizer schema * `POST /api/v1/synchronizer/data`: returns data **Optional** * `GET /logo`: returns an image/svg+xml representation of the application's logo * `POST /api/v1/synchronizer/datalist`: returns possible options for filter fields * `POST /api/v1/synchronizer/filter/validate`: performs filter fields validation * `POST /api/v1/synchronizer/resource`: returns files those behind security wall * `POST /api/v1/synchronizer/webhooks`: setting up webhook * `POST /api/v1/synchronizer/webhooks/pre-process`: verify and process incoming event * `POST /api/v1/synchronizer/webhooks/transform`: convert event payload into data that can be handled by Fibery ## **GET /** GET "/" endpoint is the main one which returns information about the app. You can find response structure in [App configuration](/guides/integrations/app-configuration). Response example: ```json theme={null} { "version": "1.0", // string representing the version of your app "name": "My Super Application", // title of the app "website": "http://myawesomeapp.com", // website "description": "All your base are belong to us!", // long description "authentication": [], // list of possible account authentication approaches "sources": [], // empty array "responsibleFor": { // app responsibility "dataSynchronization": true // indicates that app is responsible for data synchronization } } ``` **Authentication Information** The `authentication` object includes all account schema information. It informs the Fibery front-end how to build forms for the end-user to provide required account information. This property is required, even if your application does not require authentication. At least one authentication object must be provided within array. In case when application doesn't require authentication, the `fields` array must be omitted. Read more about fields in [Custom App: Fields](/guides/integrations/fields). *Important note:* if your app provides OAuth capabilities for authentication, the authentication identifiers *must* be `oauth` and `oauth2` for OAuth v1 and OAuth v2, respectively. Check [Custom App: OAuth](/guides/integrations/oauth). Only one authentication type per OAuth version is currently supported. ```json theme={null} { "authentication": [ { "id": "basic", // identifier "name": "Basic Authentication", // user-friendly title "description": "Just using a username and password", // description "fields": [ //list of fields to be filled { "id": "username", //field identifier "title": "Username", //friendly name "description": "Your username, duh!", //description "type": "text", //field type (text, password, number, etc.) "optional": true, // is this a optional field? }, /* ... */ ] } ] } ``` ## **POST /validate** This endpoint performs account validation when setting up an account for the app and before any actions that uses the account. The incoming payload includes information about the account type to be validated and all fields required: If the account is valid, the app should return HTTP status 200 with a JSON object containing a friendly name for the account: Incoming body: ```json theme={null} { "id": "basic", // identifier for the account type "fields": { //list of field values to validate according to schema "username": "test_user", "password": "test$user!", /*...*/ } } ``` Success Response: ```json theme={null} { "name": "Awesome Account" } ``` If the account is invalid, the app should return HTTP status 401 (Not Authorized) with a simple JSON object containing an error message: Failure Response: ```json theme={null} { "message": "Your password is incorrect!" } ``` **Refresh Access Token** In addition this step can be used as a possibility to refresh access token. The incoming payload includes refresh and access token, also it can include expiration datetime. Response should include new access token to override expired one. Refresh Access Token Request: ```json theme={null} { "id": "oauth2", "fields": { "access_token": "xxxx", "refresh_token": "yyyy", "expire_on": "2018-01-01" } } ``` Response sample after token refresh: ```json theme={null} { "name": "Awesome account", "access_token": "new-access-token", "expire_on": "2020-01-01" } ``` ## **POST /api/v1/synchronizer/config** The endpoint returns information about synchronization possibilities based on input parameters. It instructs Fibery about: * Available types * Available filters * Available functionalities **Request** All input parameters are optional. | Name | Type | Description | | ------- | ------ | ------------------------- | | account | object | selected account's fields | Request example: ```json theme={null} { "account": { "token": "user-token" } } ``` **Response** Response example: ```javascript theme={null} { "types": [ {"id": "bug", "name": "Bug"}, {"id": "us", "name": "User Story"}, ], "filters": [ { "id": "modifiedAfter", "title": "Modified After", "optional": true, "type": "datebox" } ] } ``` Output parameters: | Name | Type | Description | | -------- | ------------------------------ | ---------------------------------------------------------------------- | | types | `[{id: string, name: string}]` | supported types list with id and display name | | filters | Array of filters | it is used to help the user to exclude non-required data. | | webhooks | `{enabled: true, type: 'ui'}` | Optional fields that indicates that webhook functionality is supported | ### **Filter information** The `filter` object is used to help the user to exclude non-required data. Just like other field-like objects, the `filter` object is not required. If nothing is provided, users will not be able to filter out data received from the app. For more information about filters, please refer to [App configuration](/guides/integrations/app-configuration) and [Fields](/guides/integrations/fields). ## **POST /api/v1/synchronizer/schema** Integration app must provide data schema in advance so Fibery will be able to create approriate types and relations and then be able to maintain them. It should provide a schema for all requested types. Each type must contain `name` and `id` field. In additional there is a reserved field `__syncAction` that should be added to the schema if delta synchronization with possibility of removing items should be supported. **Request** Request contains: * `types` - an array of selected type ids * `filter` - currently configured filter * `account` - selected account Request example ```json theme={null} { "types": [ "pullrequest", "repository" ], "filter": { "owner": "fibery", "repositories": [ "fibery/core", "fibery/ui" ] }, "account": { "token": "token" } } ``` **Response** Includes schema for all requested types Schema is JSON object where key is field and value if field description. Field description contains: | field | description | type | | ----------- | ---------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | | id | Field id | string | | ignore | Is field visible in fields catalog | boolean | | name | Field name | string | | description | Field description | string | | readonly | Disable modify field name and type | boolean | | type | Type of field | "id", "text" ,"number" , "date", "array\[text]" Learn more in [Integration Schema types](/guides/integrations/schema-types) | | relation | Relation between types | see relations section | | subType | Optional Fibery sub type | "url" , "integer", "email", "boolean","html", "md", "files", "date-range" Learn more in [Integration Schema types](/guides/integrations/schema-types) | Response example: ```json theme={null} { "repository": { "id": { "type": "id", "name": "Id" }, "name": { "type": "text", "name": "Name" }, "url": { "type": "text", "name": "Original URL", "subType": "url" } }, "pullrequest": { "id": { "type": "id", "name": "Id" }, "name": { "type": "text", "name": "Name" }, "repositoryId": { "type": "text", "name": "Repository Id", "relation": { "cardinality": "many-to-one", "name": "Repository", "targetName": "Pull Requests", "targetType": "repository", "targetFieldId": "id" } }, "__syncAction": { "type": "text", "name": "Sync Action" } } } ``` ### **Relations** `relation` field provides a possibility to create a relation between entities in Fibery. It contains following fields: | field | description | type | | ------------- | --------------------------------- | ------------------------------------------- | | cardinality | Type of relation | "many-to-one", "many-to-many", "one-to-one" | | name | Name of the field on source side | string | | targetType | Id of target type | string | | targetName | Field name of target side | string | | targetFieldId | Find relation by value from field | string | Repository will have following fields (example includes only relation fields): * Pull Requests - Array Pull Request will have following fields: * Repository Id - string - this field will be hidden from end user * Repository - Repository Example: ```json theme={null} { "repository": { "id": { "type": "id", "name": "Id" }, "name": { "type": "text", "name": "Name" }, "url": { "type": "text", "name": "Original URL", "subType": "url" } }, "pullrequest": { "id": { "type": "id", "name": "Id" }, "name": { "type": "text", "name": "Name" }, "repositoryId": { "type": "text", "name": "Repository Id", "relation": { "cardinality": "many-to-one", "name": "Repository", "targetName": "Pull Requests", "targetType": "repository", "targetFieldId": "id" } } } } ``` **Reserved field - `__syncAction`** Sync action is reserved field that won't be visible for end user. The field is used for delta synchronization when entity should be deleted. ## **POST /api/v1/synchronizer/data** Data endpoint performs actual data retrieving for the specified integration settings. Data retrieving is run by each type independently. Data synchronization supports: * pagination * delta synchronization **Request** Inbound payload includes following information: * `types` - array of selected type ids * `requestedType` - currently fetching type * `account` - account on behalf of data should be fetched * `filter` - currently configured filters * `lastSynchronizedAt` - OPTIONAL field that indicated when last successful synchronization was run * `pagination` - OPTIONAL field includes pagination settings that was returned from previous data request * `schema` - current integration schema Request example: ```json theme={null} { "requestedType": "pullrequest", "types": ["repository", "pullrequest"], "filter": { "owner": "fibery", "repositories": ["fibery/core", "fibery/ui", "fibery/apps-gallery"] }, "account": { "token": "token" }, "pagination": { "repositories": ["fibery/ui", "fibery/apps-gallery"] }, "lastSynchronizedAt": "2020-09-30T09:08:47.074Z" "schema": { "repository": { "id": { "name": "Id", "type": "text" } } } } ``` ### **Response** Outbound payload includes: * `items` - REQUIRED array of fetched data rows * `pagination` - OPTIONAL parameter that includes information about pagination * `hasNext` - boolean attribute that indicates that there are more pages available * `nextPageConfig` - object that will be passed in `pagination` request body parameter with next page request * `synchronizationType` - OPTIONAL parameter with possible values `delta` or `full`. It indicates how data will be handled on Fibery side. If `delta` is set then only provided changes will be applied. Fibery will be looking for `__syncAction` field to identify whether row should be set or removed. If `__syncAction` equal `REMOVE` then corresponding entity will be removed. If `full` is set then unsynced data will be removed (if keep unsynced is unchecked). Response example: ```json theme={null} { "items": [ { "id": "PR_1231", "name": "Improve performance" }, { "id": "PR_1232", "name": "Fix bugs" } ], "pagination": { "hasNext": true, "nextPageConfig": { "repositories": [ "fibery/apps-gallery" ] } }, "synchronizationType": "full" } ``` ### **Errors** If something goes wrong then integration app should respond with corresponding error HTTP status code and error message. Sample of error about sync failure: ```json theme={null} { "message": "Unable to fetch data." } ``` But some errors can be fixed if try to fetch data later. In this case error body should include `tryLater` flag with value `true`. Fibery will retry this particular page later on. Sample of error about limits ```json theme={null} { "message": "Rate limits reached", "tryLater": true } ``` ## **GET /logo** `OPTIONAL` The `/logo` endpoint is used to provide a SVG representation of a connected application's logo. This endpoint is entirely optional. Valid responses are a HTTP 200 response with a `image/svg+xml` content type, a HTTP 204 (No Content) response if there is no logo, or a 302 redirect to another URI containing the logo. If no logo is provided, or an error occurs, the application will be represented with our default app logo. ## **POST /api/v1/synchronizer/datalist** `OPTIONAL` **Request** The inbound payload includes: * `types` - an array of selected type ids * `account` - selected account * `field` - name of requested field * `dependsOn` - object that contains filter key-value pairs of dependant fields Request body: ```json theme={null} { "types": [ "pullrequest", "branch" ], "account": { "token": "token" }, "field": "repository", "dependsOn": { "owner": "fibery" } } ``` This endpoint performs retrieving datalists from filter fields that marked with `datalist` flag. **Response** The response from your API should include `items` that is a JSON-serialized list of name-value objects: The `title` in each object is what is displayed to the end-user for each value in a combobox and the `value` is what is stored with the filter and what will be passed with subsequent requests that utilize the user-created filter. Response sample: ```json theme={null} { "items": [ { "title": "fibery/ui", "value": "124" }, { "title": "fibery/core", "value": "125" } ] } ``` ## **POST /api/v1/synchronizer/filter/validate** `OPTIONAL` This endpoint performs filter validation. It can be useful when app doesn't know about what filter value looks like. For example, if your app receives sql query as filter you may want to check is that query is valid. **Request** Request body contains: * `types` - array of selected type ids * `account` - account on behalf of data should be fetched * `filter` - currently configured filters Request example: ```json theme={null} { "types": [ "repository", "pullrequest" ], "filter": { "owner": "fibery", "repositories": [ "fibery/core", "fibery/ui", "fibery/apps-gallery" ] }, "account": { "token": "token" } } ``` **Response** If the filter is valid, the app should return HTTP status 200 or 204. If the account is invalid, the app should return HTTP status 400 (Bad request) with a simple JSON object containing an error message: Error response sample: ```json theme={null} { "message": "Your filter is incorrect!" } ``` ## POST /api/v1/synchronizer/resource `OPTIONAL` This endpoint is used to access files that require authentication. For example, if the schema contains a `files` field and the files are not accessible by direct link then this route can be used to download files by specifying a url as `app://resource?url=:url&value=1`. As a result the route will be called with `url` and `value` params. The resource endpoint is called with all query parameters specified in `app://resource` url. **Request** Request body contains: * `types` - array of selected type ids * `account` - account on behalf of data should be fetched * `filter` - currently configured filters * `params` - list of parameters. **Response** File content as a stream ## POST /api/v1/synchronizer/webhooks `OPTIONAL` The endpoint is responsible for installing, updating or reinstalling a webhook based on provided parameters. It's the place where webhook based configuration starts. **Request**. It accepts following parameters: * `types` - an array of selected types ids * `filter` - currently configured filter * `account` - authenticated external account * `webhook` - OPTIONAL. If it's `null` then a new webhook is going to be installed, otherwise it will contain current webhook configuration (that is returned by THIS endpoint) ```json theme={null} { "types": [ "pullrequest", "repository" ], "filter": { "owner": "fibery", "repositories": [ "fibery/ui", "fibery/core" ] }, "account": { "token": "token" }, "webhook": null } ``` **Response:** Response is a JSON object with following required fields * `id` - some random id * `workspaceId` - 3rd party workspace id linked to webhook/synchronization (e.g. slack workspace, intercom workspace, gitlab organization) * any other data that you want to receive on reinstall event. ```json theme={null} { "id": "webhook_id", "workspaceId": "workspaceId", "events": [ "create_repo", "delete_repo" ] } ``` ## POST /api/v1/synchronizer/webhooks/pre-process `OPTIONAL` The route is responsible for initial pre processing of request. Data that is received by this route is the same as 3rd party service sends without any changes (almost). It's done to be able to calculate request signature properly. Goals of the service: * verify incoming request (usually it means that service should verify signature based on 3rd party system chosen algorithm, e.g. [hubspot webhook verification](https://developers.hubspot.com/beta-docs/guides/apps/authentication/validating-requests#validating-requests-from-hubspot)) * response with data that should be sent to 3rd party caller and list of workspace ids this event will work for. HTTP status code of the response will also be forwarded to 3rd party system **Request** Request payload and headers depends on 3rd party service **Response** Response should include following fields: * `reply` - data will be send as a response to 3rd party caller * `workspaceIds` - array of workspace ids this event can be applied on. Array can be empty ```json theme={null} { "reply": { "challenge": "value" }, "workspaceIds": [ "team-a", "team-b" ] } ``` ## POST /api/v1/synchronizer/webhooks/transform `OPTIONAL` This route is responsible for converting event payload into data that can be handled by Fibery. Data is applied by `delta` synchronization rules. **Request** Request payload includes: * `params` - event headers that comes from external system. * `payload` - event body that comes from external system. * `types` - an array of selected type ids * `filter` - currently configured filter * `account` - selected account ```json theme={null} { "params": { "x-github-id": "1234", "x-github-signature256": "dsadsa" }, "payload": { "action": "create", "repository": { "id": "repo1" } }, "types": [ "pullrequest", "repository", "branch" ], "filter": { "owner": "fibery", "repositories": [] }, "account": { "token": "token" } } ``` **Response** Response includes `data` object that is a map of data arrays by type id. ```json theme={null} { "data": { "repositories": [ { "id": "repo1", "name": "Repo1", "__syncAction": "SET" } ], "branches": [ { "id": "master", "name": "master", "repositoryId": "repo1", "__syncAction": "SET" } ] } } ``` # Integration Schema types Source: https://developers.fibery.com/guides/integrations/schema-types Learn how to map integration field types to Fibery Field types. You can create your own [Integration templates](https://the.fibery.io/@public/User_Guide/Guide/Integration-templates-68) and sync data from any external system. In terms of integration, a Fibery Field type is represented as pair of parameters in the integration schema: * `type` * `subType` Below the list of available combinations.
Fibery field type Integration type subType Comments
`fibery/decimal` `number`

It's possible to apply number optional formatting though field configuration. Formatting is applied only for newly created fields. Formatting can be changed via default Fibery fields UI once field is created.

Money

```json theme={null} { "id": "amount", "name": "Amount", "type": "number", "format": { "format": "Money", "currencyCode": "EUR", "hasThousandSeparator": true, "precision": 2 } } ```

Percent

```json theme={null} { "id": "percent", "name": "Percent", "type": "number", "format": { "format": "Percent", "precision": 2 } } ```

Number

```json theme={null} { "id": "value", "name": "Value", "type": "number", "format": { "format": "Number", "unit": "ea", "hasThousandSeparator": true, "precision": 2 } } ```
`fibery/integer` `number` `integer`
`fibery/text` `text`

It's possible to apply text optional formatting though field configuration. Formatting is applied only for newly created fields. Formatting can be changed via default Fibery fields UI once field is created.

Phone

```json theme={null} { "id": "phone", "name": "Phone", "type": "text", "format": { "format": "phone" } } ```
`fibery/url` `text` `url`
`fibery/email` `text` `email`
`fibery/bool` `text` `boolean`

The conversion from text to boolean is as follows:

```json theme={null} { "true": true, "yes": true, "on": true, "1": true, "false": false, "no": false, "off": false, "0": false, "checked": true, "": false } ```
`fibery/date-range` `text` `date-range`

The value is a stringified object with start and end fields.

```json theme={null} { "start": "2020-01-22", "end": "2020-08-19" } ```
`fibery/date-time-range` `text` `date-time-range`

The value is a stringified object with start and end fields.

```json theme={null} { "start": "2020-01-22T01:02:23.977Z", "end": "2020-08-18T06:02:23.977Z" } ```
`Collaboration~Documents/Document` `text` `html` Replaces content of rich text field converting value from `html` format.
`Collaboration~Documents/Document` `text` `md` Replaces content of rich text field converting value from `md` format.
Icons extension `text` `icon` Can be used to set Icon of entity. At the moment it works only with Emojis. The value should be either a native emoji (i.e. πŸ‘‹πŸ») or its alias (i.e. `:wave::skin-tone-2:`).
Single Select Enum `text` `single-select`

It's possible to specify options by adding options field into schema field configuration.

```json theme={null} { "options": [ { "name": "Open", "icon": "laughing", "color": "#f2e2f4" }, { "name": "In Progress" }, { "name": "Closed" } ] } ```

If options property is missing then the integration module will infer the options automatically based on your data.

Workflow `text` `workflow`

It's possible to specify options by adding options field into schema field configuration. Options must include record with default: true and final: true records. It could optionally include type: "Not started" | "Started" | "Finished".

```json theme={null} { "options": [ { "name": "Open", "icon": "laughing", "color": "#f2e2f4", "default": true }, { "name": "In Progress", "type": "Started" }, { "final": true, "name": "Closed" } ] } ```

If options property is missing then the integration module will infer the options automatically based on your data, including start and final options.

Multi Select Enum `text` or `array[text]` `multi-select`

It's possible to specify options by adding options field into schema field configuration.

```json theme={null} { "options": [ { "name": "JS", "icon": "laughing", "color": "#f2e2f4" }, { "name": "Java" }, { "name": "Closure" } ] } ```

If options property is missing then the integration module will infer the options automatically based on your data.

Values can be passed differently depending on type value. It can be either a JSON array \["JS", "Java"] of selected options or a comma-separated string of selected options "JS,Java".

`fibery/date-time` `date` Value format: `2020-01-22T01:02:23.977Z`.
`fibery/date` `date` `day` Value format: `2020-08-22`.
Files `array[text]` β€” for multiple files, `text` β€” for single file `file`

Array of links to files. The integration service will download files from the links and upload them into Fibery. If access to file content requires authentication then the url should be provided in special format via app\://resource and connector should implement POST /api/v1/synchronizer/resource endpoint. See here for more info.

By default, files are treated as unique by provided URL. So if file url is changed during next sync then previous file will be deleted and new file will be uploaded. Unfortunately, it's rather common practice to provide temporary file URL. In this case Fibery will be constantly remove and add the same file during each sync. Luckily, Fibery provides a way to add an unique key for each file. Connector developers may add a special query parameter \_\_file-key into file url. It's also works with authenticated access by adding the same query parameter (\_\_file-key).

Example: [https://myapp/files/temp-file-access-token?\_\_file-key=file-id](https://myapp/files/temp-file-access-token?__file-key=file-id) and on next sync [https://myapp/files/temp-file-access-token-2?\_\_file-key=file-id](https://myapp/files/temp-file-access-token-2?__file-key=file-id).

Avatar extension `text` `avatar` Link to file. Integration will download the file from the link and upload it into Fibery.
`fibery/location` `text` `location`

Location field. Supported values are:

Coordinates

``` 40.123, -74.123 40.123Β° N 74.123Β° W 40Β° 7Β΄ 22.8" N 74Β° 7Β΄ 22.8" W 40Β° 7.38’ , -74Β° 7.38’ N40Β°7’22.8, W74Β°7’22.8" 40Β°7’22.8"N, 74Β°7’22.8"W 40 7 22.8, -74 7 22.8 40.123 -74.123 40.123Β°,-74.123Β° 144442800, -266842800 40.123N74.123W 4007.38N7407.38W 40Β°7’22.8"N, 74Β°7’22.8"W 400722.8N740722.8W N 40 7.38 W 74 7.38 40:7:23N,74:7:23W 40:7:22.8N 74:7:22.8W 40Β°7’23"N 74Β°7’23"W 40Β°7’23" -74Β°7’23" 40d 7’ 23" N 74d 7’ 23" W 40.123N 74.123W 40Β° 7.38, -74Β° 7.38 ```

Stringified JSON

```json theme={null} { "longitude": "52.2297", "latitude": "21.0122", "fullAddress": "Warsaw, Poland" } ```
## Special cases ### Title field By default, the integration will use the Field with id `name` as the title field (equivalent to the Name Field in standard databases) but it is possible to override this by adding the `subType: title` annotation. ```json theme={null} { "commitName": { "type": "text", "name": "Commit Name", "subType": "title" } } ``` ### People relations ```json theme={null} { "project": { "id": { "name": "Id", "type": "id" }, "owner": { "name": "Owner", "type": "text", "relation": { "kind": "native", "targetType": "fibery/user", "cardinality": "many-to-one", "targetName": "My Projects", "targetFieldId": "user/email" } }, "assignees": { "name": "Assignees", "type": "array[text]", "relation": { "kind": "native", "targetType": "fibery/user", "cardinality": "many-to-many", "targetName": "Assigned To", "targetFieldId": "user/email" } } } } ``` `targetFieldId` possible values: * `user/email` - finds users by email address (case sensitive) * in all other case will use `Name` field to find a user ### Integration relations Let's assume that there are two types: `Branch` and `Project`. So `Project` includes many `Branches`. It can be configured in the following way ```json theme={null} { "project": { "id": { "name": "Id", "type": "id" } // other fields }, "branch": { "id": { "name": "Id", "type": "id" }, "projectId": { "name": "Project Id", "type": "text", "relation": { "cardinality": "many-to-one", "name": "Project", "targetType": "project", "targetFieldId": "id", "targetName": "Branches" } } // other fields } } ``` In this case, the integration will create following fields: * `Project Id` field with `text` type in the `Branch` type. This field is hidden field and used for auto-linking relations. * `Project` field in `Branch` type. It's a relation field to the `Project` type. * `Branches` collection field in the `Project` type. It's another side of `Project` β†’ `Branch` relation. Possible cardinalities: * `many-to-one` * `many-to-many` * `one-to-one` # Test and debug Source: https://developers.fibery.com/guides/integrations/test-and-debug Test and debug a custom integration app locally. ## **Expose local instance** It is possible to run your app on local machine and make the app's url publicly available by using tools like [ngrok](https://ngrok.com/). Then you will have an ability to debug the app locally after adding it Fibery apps gallery. Don't forget to remove the app from Fibery integration apps catalog after testing. Expose local instance to world: ```bash theme={null} brew install ngrok ngrok http 8080 ``` ## **Integration tests** It is recommended to create integration tests before adding your custom app to Fibery apps gallery. Check some tests I created for holidays app. ```javascript theme={null} const request = require(`supertest`); const app = require(`./app`); const assert = require(`assert`); const _ = require(`lodash`); describe(`integration app suite`, function () { it(`should have the logo`, async () => { await request(app).get(`/logo`) .expect(200) .expect(`Content-Type`, /svg/); }); it(`should have app config`, async () => { const {body: appConfig} = await request(app).get(`/`) .expect(200).expect(`Content-Type`, /json/); assert.equal(appConfig.name, `Public Holidays`); assert.match(appConfig.description, /public holidays/); assert.equal(appConfig.responsibleFor.dataSynchronization, true); }); it(`should have validate end-point`, async () => { const {body: {name}} = await request(app).post(`/validate`) .expect(200).expect(`Content-Type`, /json/); assert.equal(name, `Public`); }); it(`should have synchronization config`, async () => { const {body: {types, filters}} = await request(app) .post(`/api/v1/synchronizer/config`) .expect(200) .expect(`Content-Type`, /json/); assert.equal(types.length, 1); assert.equal(filters.length, 3); }); it(`should have schema holidays type defined`, async () => { const {body: {holiday}} = await request(app) .post(`/api/v1/synchronizer/schema`) .send() .expect(200) .expect(`Content-Type`, /json/); assert.deepEqual(holiday.id, {name: `Id`, type: `id`}); }); it(`should return data for CY`, async () => { const {body: {items}} = await request(app) .post(`/api/v1/synchronizer/data`) .send({ requestedType: `holiday`, filter: { countries: [`CY`], } }).expect(200).expect(`Content-Type`, /json/); assert.equal(items.length > 0, true); const holiday = items[0]; assert.equal(holiday.id.length > 0, true); assert.equal(holiday.name.length > 0, true); }); it(`should return data for BY and 2020 year only`, async () => { const {body: {items}} = await request(app) .post(`/api/v1/synchronizer/data`) .send({ requestedType: `holiday`, filter: { countries: [`BY`], from: 2020, to: 2020 } }).expect(200).expect(`Content-Type`, /json/); assert.equal(items.length > 0, true); const holidaysOtherThan2020 = _.filter(items, (i) => new Date(i.date).getFullYear() !== 2020); assert.equal(holidaysOtherThan2020.length > 0, false); }); }); ``` # Tutorial: Holidays app Source: https://developers.fibery.com/guides/integrations/tutorial-holidays-app Learn how to build a custom integration app step by step. Integrations are ready-made services that make it simple to configure and customize specific processes. You can create your own [Integration templates](https://the.fibery.io/@public/User_Guide/Guide/Integration-templates-68) and sync data from any external system. Fibery has a lot of built-in integrations like Jira, Trello, Github, etc. but sometimes the need arises to integrate custom data. In this article, we will show how to create simple integration app which does not require any authentication. Let's imagine we intend to create a [public holidays app](https://gitlab.com/fibery-community/holidays-integration-app) which will sync data about holidays for selected countries. The holidays service [https://date.nager.at](https://date.nager.at/) will be used to retrieve holidays. For holidays, we have a [Sync public holidays from Google Calendar](https://the.fibery.io/@public/User_Guide/Guide/Sync-public-holidays-from-Google-Calendar-138) native integration, if you need it. ## **Getting Started** All communication between an integration application and Fibery services is done via standard hypertext protocols, whether that be HTTP or HTTPS. Please check out the full documentation starting from the [Integrations overview](/guides/integrations/overview). The choice of underlying technologies used to develop integration applications is up to the individual developer. We are going to implement all required endpoints in web app step by step. We will use Node.js for implementing this integration app. The source code can be found [here](https://gitlab.com/fibery-community/holidays-integration-app). ### **App configuration endpoint** Every integration should have the configuration which describes what the app is doing and the authentication methods. The [app configuration](/guides/integrations/app-configuration) should be accessible at **GET "/"** endpoint and should be publicly available. For example, we used Heroku to host the app. This is the [endpoint implementation.](https://gitlab.com/fibery-community/holidays-integration-app/-/blob/master/app.js#L34-35) ```javascript theme={null} const appConfig = require(`./config.app.json`); app.get(`/`, (req, res) => res.json(appConfig)); ``` This is how [config.app.json](https://gitlab.com/fibery-community/holidays-integration-app/-/blob/master/config.app.json) looks like. ```json theme={null} { "id": "holidays-app", "name": "Public Holidays", "version": "1.0.1", "description": "Integrate data about public holidays into Fibery", "authentication": [ { "id": "public", "name": "Public Access", "description": "There is no any authentication required", "fields": [] } ], "sources": [], "responsibleFor": { "dataSynchronization": true } } ``` All properties are required. Find the information about all properties [here](/guides/integrations/rest-endpoints#get-/). Since we don't want my app be authenticated, we didn't provide any fields for "Public Access" node in authentication. It means that any user will be able to connect their account to the public holidays app. Find an example with token authentication [here](https://gitlab.com/fibery-community/integration-sample-apps/-/blob/master/samples/simple/src/app.js). Note that case matters. ### **Validate** This endpoint is [responsible for app account validation](/guides/integrations/rest-endpoints#post-/validate). It is required to be implemented. Let's just send back the name of account without any authentication since we are creating an app with public access. POST /validate ```javascript theme={null} app.post(`/validate`, (req, res) => res.json({name: `Public`})); ``` ### **Sync configuration endpoint** The way data is synchronised should be described. The endpoint is **POST /api/v1/synchronizer/config** ```javascript theme={null} const syncConfig = require(`./config.sync.json`); app.post(`/api/v1/synchronizer/config`, (req, res) => res.json(syncConfig)); ``` [config.sync.json](https://gitlab.com/fibery-community/holidays-integration-app/-/blob/master/config.sync.json) ```json theme={null} { "types": [ { "id": "holiday", "name": "Public Holiday" } ], "filters": [ { "id": "countries", "title": "Countries", "datalist": true, "optional": false, "type": "multidropdown" }, { "id": "from", "type": "number", "title": "Start Year (by default previous year used)", "optional": true }, { "id": "to", "type": "number", "title": "End Year (by default current year used)", "optional": true } ] } ``` **The types** are responsible for describing types which will be synced. For the holidays app it is just one type with id "holidays" and name "Public Holidays". It means that only one integration Fibery database will be created in the space, with the name "Public Holidays". **The filters** contain information on how the type can be filtered. In our case, there is a multi drop down ('countries') which is required and marked as data list. It means that options for this drop down should be retrieved from app and special end-point should be implemented for that. Also, we have two numeric filters from and to which are optional and can be used to filter holidays by years . Find information about filters in [App configuration](/guides/integrations/app-configuration). ### **Datalist** Endpoint [POST /api/v1/synchronizer/datalist](/guides/integrations/rest-endpoints#post-/api/v1/synchronizer/datalist) should be implemented if synchronizer filters has dropdown marked as "datalist": true. Since we have countries multi drop down which should contain countries it is required to [implement the mentioned endpoint](https://gitlab.com/fibery-community/holidays-integration-app/-/blob/master/app.js#L45-48) as well. ```javascript theme={null} app.post(`/api/v1/synchronizer/datalist`, wrap(async (req, res) => { const countries = await (got(`https://date.nager.at/api/v3/AvailableCountries`).json()); const items = countries.map((row) => ({title: row.name, value: row.countryCode})); res.json({items}); })); ``` For this app, only the list of countries is returned since our config has only one data list. In the case where there are several data lists then we will need to retrieve "field" from request body which will contain an id of the requested list. The response should be formed as an array of items where every element contains title and value properties. For example, part of countries response will look like this: ```json theme={null} { "items": [ { "title": "Poland", "value": "PL" }, { "title": "Belarus", "value": "BY" }, { "title": "Cyprus", "value": "CY" }, { "title": "Denmark", "value": "DK" }, { "title": "Russia", "value": "RU" } ] } ``` ### **Schema** [POST /api/v1/synchronizer/schema](/guides/integrations/rest-endpoints#post-/api/v1/synchronizer/schema) endpoint should return the data schema of the app. In our case it should contain only one root element ["holiday"](https://gitlab.com/fibery-community/holidays-integration-app/-/blob/master/schema.json) named after the id of holiday type in sync configuration above. ```javascript theme={null} const schema = require(`./schema.json`); app.post(`/api/v1/synchronizer/schema`, (req, res) => res.json(schema)); ``` schema.json content can be found below ```json theme={null} { "holiday": { "id": { "name": "Id", "type": "id" }, "name": { "name": "Name", "type": "text" }, "date": { "name": "Date", "type": "date" }, "countryCode": { "name": "Country Code", "type": "text" } } } ``` Every schema type should have `id` and `name` elements defined. ### **Data** The data endpoint is responsible for retrieving data. Check the documentation on [how request body looks](/guides/integrations/rest-endpoints#post-/api/v1/synchronizer/data). There is no paging needed in case of our app, so the data is returned according to selected countries and years interval. The source code can be found [here](https://gitlab.com/fibery-community/holidays-integration-app/-/blob/master/app.js#L51-73). **POST /api/v1/synchronizer/data** First, a small helper that resolves the year range from filter values (defaulting to last year through this year): ```javascript theme={null} const getYearRange = filter => { let fromYear = parseInt(filter.from); let toYear = parseInt(filter.to); if (_.isNaN(fromYear)) { fromYear = new Date().getFullYear() - 1; } if (_.isNaN(toYear)) { toYear = new Date().getFullYear(); } const yearRange = []; while (fromYear <= toYear) { yearRange.push(fromYear); fromYear++; } return yearRange; }; ``` Then the data endpoint itself: ```javascript theme={null} app.post(`/api/v1/synchronizer/data`, wrap(async (req, res) => { const {requestedType, filter} = req.body; if (requestedType !== `holiday`) { throw new Error(`Only holidays database can be synchronized`); } if (_.isEmpty(filter.countries)) { throw new Error(`Countries filter should be specified`); } const {countries} = filter; const yearRange = getYearRange(filter); const items = []; for (const country of countries) { for (const year of yearRange) { const url = `https://date.nager.at/api/v3/PublicHolidays/${year}/${country}`; console.log(url); (await (got(url).json())).forEach((item) => { item.id = uuid(JSON.stringify(item)); items.push(item); }); } } return res.json({items}); })); ``` The requestedType and filter can be retrieved from the request body. The response should be returned as array in "items" element. ```json theme={null} {"items": []} ``` ## Testing custom integration app 1. It is recommended to create integration tests before adding your custom app to Fibery apps gallery. Check some tests I created for holidays app [here](https://gitlab.com/fibery-community/holidays-integration-app/-/blob/master/test.js). 2. It is possible to run your app on local machine and make the app's url publicly available by using tools like [ngrok](https://ngrok.com/). ```bash theme={null} brew install ngrok ngrok http 8080 ``` Then you will have an ability to debug the app locally after adding it Fibery apps gallery. Don't forget to remove the app from Fibery integration apps catalog after testing. Find the source code of this app and other examples in [our public gitlab repository](https://gitlab.com/fibery-community). An integration sync could cleanup (delete) entities which is potentially quite dangerous, since restoration is not super easy. However, integration entities with ANY schema modifications will not be deleted during sync. This applies if: * At least one custom field has been added by an admin. * The entity has any rich text field. To allow users to manage source-deleted entities, there is a Simple Text field `deleted in source` with values `yes` and `no`. If an entity is deleted in the source, the value will be set to `yes`. Admins can then manually delete these entities if needed (manually or via automation). ## FAQ #### Where can I ask questions regarding API & Integrations? The best place is to ping us in chat [or in our community](https://community.fibery.io/c/api-programming/10 "https://community.fibery.io/c/api-programming/10"). #### Is there any way to pass batches of entity data to an external action? No, there are no batch actions available\ However, batch actions can be implemented in a custom app by accumulating incoming items into app storage and performing actions on them as a batch.\ Please, note that the Automation API calls custom actions for Entities one by one in queue. That means, that the whole execution will be stopped if the first action fails. ### Community examples: * [Toggl integration](https://community.fibery.io/t/fibery-toggl-integration/4151 "https://community.fibery.io/t/fibery-toggl-integration/4151") from Reify academy * [Zotero](https://community.fibery.io/t/zotero-integration/1802 "https://community.fibery.io/t/zotero-integration/1802") from Seaotternerd # Tutorial: Notion sync Source: https://developers.fibery.com/guides/integrations/tutorial-notion-sync Build a Notion sync integration with OAuth and dynamic schema. This tutorial is created in order to provide help on creating complex integration app with dynamic data schema, non-primitive data synchronization (for example, files) and oauth2 authentication. The source code (node.js) can be found in [official Fibery repository](https://gitlab.com/fibery-community/notion-app) which contains the implementation of integrating [Notion](https://notion.so/) databases into Fibery databases. Demo databases can be found [here](https://fibery-dev.notion.site/fibery-dev/Demo-cc147e7b2af04d259ccd98444c67b9b4). ## **App Configuration** Returns the description of the app and possible ways to be authenticated in Notion. Route in app.js ```javascript theme={null} app.get(`/`, (req, res) => res.json(connector())); ``` connector.config.js: ```javascript theme={null} const config = require(`./config`); const ApiKeyAuthentication = { description: `Please provide notion authentication`, name: `Token`, id: `key`, fields: [ { type: `password`, name: `Integration Token`, description: `Provide Notion API Integration Token`, id: `key`, }, { type: `link`, value: `https://www.notion.so/help/create-integrations-with-the-notion-api`, description: `We need to have your Notion Integration Token to synchronize the data.`, id: `key-link`, name: `Read how to create integration, grant access and create token here...`, }, ], }; const OAuth2 = { id: 'oauth2', name: 'OAuth v2 Authentication', description: 'OAuth v2-based authentication and authorization for access to Notion', fields: [ { title: 'callback_uri', description: 'OAuth post-auth redirect URI', type: 'oauth', id: 'callback_uri', }, ], }; const getAuthenticationStrategies = () => { return [OAuth2, ApiKeyAuthentication]; }; module.exports.connector = () => ({ id: `notion-app`, name: `Notion`, version: config.version, website: `https://notion.com`, description: `More than a doc. Or a table. Customize Notion to work the way you do.`, authentication: getAuthenticationStrategies(), responsibleFor: { dataSynchronization: true, }, sources: [], }); ``` As you see there are two authentication ways are defined: ### **OAuth2** Hardcoded `"oauth2"` should be used as `id` in case you would like to implement OAuth2 support in integration app. image.png ### **Token Authentication** You may use special field `type: "link"` in order to provide url for external resource where the user can get more info. Use `type:"password"` for tokens or other text fields which need to be secured. image.png ## **Token Authorization** The implementation of token authentication is the simplest way to implement. We always used it for testing and development since it is not required UI interaction. The request contains `id` of auth and user provided values. In our case it is `key`. Other fields are appended by system and can be ignored. Route (app.js): ```javascript theme={null} app.post(`/validate`, (req, res) => promiseToResponse(res, notion.validate(_.get(req, `body.fields`) || req.body))); ``` Request Body: ```json theme={null} { "id": "key", "fields": { "app": "620a3c9baec5dd25794fed7a", "auth": "key", "owner": "620a3c46cf7154924cf442cb", "key": "MY TOKEN", "enabled": true } } ``` Notion call (the name of account is returned): ```javascript theme={null} module.exports.validate = async (account) => { const client = getNotionClient(account); const me = await client.users.me(); return {name: me.name}; // response should include the name of user account }; ``` ## **OAuth 2** OAuth 2 is a bit more complex and requires several routes to be implemented. The `POST /oauth2/v1/authorize` endpoint performs the initial setup for OAuth version 2 accounts using `Authorization Code` grant type by generating `redirect_uri` based on received parameters. Read more in [Custom App: OAuth](/guides/integrations/oauth). The `POST /oauth2/v1/access_token` endpoint performs the final setup and validation of OAuth version 2 accounts. Information as received from the third party upon redirection to the previously posted `callback_uri` are sent to this endpoint, with other applicable account information, for final setup. app.js ```javascript theme={null} app.post('/oauth2/v1/authorize', (req, res) => { try { const {callback_uri: callbackUri, state} = req.body; const redirectUri = oauth.getAuthorizeUrl(callbackUri, state); res.json({redirect_uri: redirectUri}); } catch (err) { res.status(401).json({message: `Unauthorized`}); } }); app.post('/oauth2/v1/access_token', async (req, res) => { try { const tokens = await oauth.getAccessToken(req.body.code, req.body.fields.callback_uri); res.json(tokens); } catch (err) { res.status(401).json({message: 'Unauthorized'}); } }); ``` oauth.js ```javascript theme={null} const got = require(`got`); const CLIENT_ID = process.env.ENV_CLIENT_ID; const CLIENT_SECRET = process.env.ENV_CLIENT_SECRET; module.exports = { getAuthorizeUrl: (callbackUri, state) => { const queryParams = { state, redirect_uri: callbackUri, response_type: 'code', client_id: CLIENT_ID, owner: `user`, }; const queryParamsStr = Object.keys(queryParams) .map((key) => `${encodeURIComponent(key)}=${encodeURIComponent(queryParams[key])}`) .join(`&`); return `https://api.notion.com/v1/oauth/authorize?${queryParamsStr}`; }, getAccessToken: async (code, callbackUri) => { const tokens = await got.post(`https://api.notion.com/v1/oauth/token`, { resolveBodyOnly: true, headers: { "Authorization": `Basic ${Buffer.from(`${CLIENT_ID}:${CLIENT_SECRET}`).toString('base64')}`, }, json: { code, redirect_uri: callbackUri, grant_type: `authorization_code`, }, }).json(); return {access_token: tokens.access_token}; }, }; ``` The implementation of oauth is pretty similar for many services and Notion is not exclusion here. Find the code of oauth.js in the right code panel. `access_token` will be passed into `/validate` for validating token in future calls. ## **Synchronizer configuration** This endpoint returns types which should be synced to Fibery databases. In Notion case it is the list of databases. Static `user` type is added. Check how the configuration response looks like for [Notion Demo](https://fibery-dev.notion.site/fibery-dev/Demo-cc147e7b2af04d259ccd98444c67b9b4). image.png app.js (route) ```javascript theme={null} app.post(`/api/v1/synchronizer/config`, (req, res) => { if (_.isEmpty(req.body.account)) { throw new Error(`account should be provided`); } promiseToResponse(res, notion.config(req.body)); }); ``` notion.api.js ```javascript theme={null} const getDatabases = async ({account, pageSize = 1000}) => { const client = getNotionClient(account); let hasNext = true; let start_cursor = null; const databases = []; while (hasNext) { const args = { page_size: pageSize, filter: { value: `database`, property: `object`, } }; if (start_cursor) { args.start_cursor = start_cursor; } const {results, has_more, next_cursor} = await client.search(args); results.forEach((db) => databases.push(db)); hasNext = has_more; start_cursor = next_cursor; } return databases; }; const getDatabaseItem = (db) => { const name = _.get(db, `title[0].plain_text`, `Noname`).replace(/[^\w ]+/g, ``).trim(); return {id: db.id, name}; }; module.exports.config = async ({account, pageSize}) => { const databases = await getDatabases({account, pageSize}); const dbItems = databases.map((db) => getDatabaseItem(db)).concat({id: `user`, name: `User`}); return {types: dbItems, filters: []}; }; ``` Response example ```json theme={null} { "types": [ { "id": "f4642444-220c-439d-85d6-378ddff3d510", "name": "Features" }, { "id": "3bd058e6-a71c-4e9a-8480-a76810ae38d3", "name": "Tasks" }, { "id": "user", "name": "User" } ], "filters": [] } ``` ## **Schema of synchronization** The schema which describes fields and relations should be provided for each sync type. Find [full implementation here](https://gitlab.com/fibery-community/notion-app/-/blob/main/app/notion.api.js#L156). It is not easy thing to implement since we are talking about dynamic data in Notion databases. app.js (schema route) ```javascript theme={null} app.post(`/api/v1/synchronizer/schema`, (req, res) => promiseToResponse(res, notion.schema(req.body))); ``` notion.api.js ```javascript theme={null} module.exports.schema = async ({account, types}) => { const databases = await getDatabases({account}); const mapDatabasesById = _.keyBy(databases, `id`); const schema = {}; types.forEach((id) => { if (id === `user`) { schema.user = userSchema; return; } const db = mapDatabasesById[id]; if (_.isEmpty(db)) { throw new Error(`Database with id "${id}" is not found`); } schema[id] = createSchemaFromDatabase(db); }); cleanRelationsDuplication(schema); return schema; }; ``` Request example: ```json theme={null} { "account": { "_id": "620a4396aec5dd672c4fed83", "access_token": "USER-TOKEN", "app": "620a3c9baec5dd25794fed7a", "auth": "oauth2", "owner": "620a3c46cf7154924cf442cb", "enabled": true, "name": "Fibery Developer", "masterAccountId": null, "lastUpdatedOn": "2022-02-21T09:45:37.802Z" }, "filter": {}, "types": [ "f4642444-220c-439d-85d6-378ddff3d510", "3bd058e6-a71c-4e9a-8480-a76810ae38d3", "user" ] } ``` Response example: ```json theme={null} { "f4642444-220c-439d-85d6-378ddff3d510": { "id": { "type": "id", "name": "Id" }, "archived": { "type": "text", "name": "Archived", "subType": "boolean" }, "created_time": { "type": "date", "name": "Created On" }, "last_edited_time": { "type": "date", "name": "Last Edited On" }, "__notion_link": { "type": "text", "name": "Notion Link", "subType": "url" }, "related to tasks (column)": { "name": "Related to Tasks (Column) Ref", "type": "text", "relation": { "cardinality": "many-to-many", "targetFieldId": "id", "name": "Related to Tasks (Column)", "targetName": "Feature", "targetType": "3bd058e6-a71c-4e9a-8480-a76810ae38d3" } }, "tags": { "name": "Tags", "type": "array[text]" }, "due date": { "name": "Due Date", "type": "date" }, "name": { "name": "Name", "type": "text" } }, "3bd058e6-a71c-4e9a-8480-a76810ae38d3": { "id": { "type": "id", "name": "Id" }, "archived": { "type": "text", "name": "Archived", "subType": "boolean" }, "created_time": { "type": "date", "name": "Created On" }, "last_edited_time": { "type": "date", "name": "Last Edited On" }, "__notion_link": { "type": "text", "name": "Notion Link", "subType": "url" }, "status": { "name": "Status", "type": "text" }, "assignees": { "name": "Assignees Ref", "type": "array[text]", "relation": { "cardinality": "many-to-many", "targetType": "user", "targetFieldId": "id", "name": "Assignees", "targetName": "Tasks (Assignees Ref)" } }, "specs": { "name": "Specs", "type": "array[text]", "subType": "file" }, "link to site": { "name": "Link to site", "type": "text", "subType": "url" }, "name": { "name": "Name", "type": "text" } }, "user": { "id": { "type": "id", "name": "Id", "path": "id" }, "name": { "type": "text", "name": "Name", "path": "name" }, "type": { "type": "text", "name": "Type", "path": "type" }, "email": { "type": "text", "name": "Email", "subType": "email" } } } ``` It can be noticed that almost any field from Notion database can be mapped into Fibery field using `subType` attribute. Relations can be mapped as well. Rich text can be sent as `html` or `md` by defining corresponding `type="text"` and `subType="md" or "html"`. Note: Relation between databases(types) should be declared only once. Double declarations for relations will lead to duplication of relations in Fibery databases. We implemented the function `cleanRelationsDuplication` in order to remove redundant relation declarations from schema fields. Files field mapping: ```json theme={null} "specs": { "name": "Specs", "type": "array[text]", "subType": "file" } ``` ## **Data route** Notion supports paged output, so it is handy to fetch data page by page. The response should include `pagination` node with `hasNext` equals to `true` or `false` and `nextPageConfig` (next page configuration) which will be included with the future request as `pagination`. You may notice that we have included `schema` into `nextPageConfig` (pagination config). It is not required and it is done as an optimization in order to save some between pages fetching on schema resolving. In other words the pagination can be used as a context cache between page calls. app.js ```javascript theme={null} app.post(`/api/v1/synchronizer/data`, (req, res) => promiseToResponse(res, notion.data(req.body))); ``` notion.api.js (paging support) ```javascript theme={null} const getValue = (row, {path, arrayPath, subPath = ``}) => { let v = null; const paths = _.isArray(path) ? path : [path]; paths.forEach((p) => { if (!_.isUndefined(v) && !_.isNull(v)) { return; } v = _.get(row, p); }); if (!_.isEmpty(subPath) && _.isObject(v)) { return getValue(v, {path: subPath}); } if (!_.isEmpty(arrayPath) && _.isArray(v)) { return v.map((element) => getValue(element, {path: arrayPath})); } if (_.isObject(v)) { if (v.start) { return v.start; } if (v.end) { return v.end; } if (v.type) { return v[v.type]; } return JSON.stringify(v); } return v; }; const processItem = ({schema, item}) => { const r = {}; _.keys(schema).forEach((id) => { const schemaValue = schema[id]; r[id] = getValue(item, schemaValue); }); return r; }; const resolveSchema = async ({pagination, client, requestedType}) => { if (pagination && pagination.schema) { return pagination.schema; } if (requestedType === `user`) { return userSchema; } return createSchemaFromDatabase(await client.databases.retrieve({database_id: requestedType})); }; const createArgs = ({pageSize, pagination, requestedType}) => { const args = { page_size: pageSize, }; if (!_.isEmpty(pagination) && !_.isEmpty(pagination.start_cursor)) { args.start_cursor = pagination.start_cursor; } if (requestedType !== `user`) { args.database_id = requestedType; } return args; }; module.exports.data = async ({account, requestedType, pageSize = 1000, pagination}) => { const client = getNotionClient(account); const schema = await resolveSchema({pagination, client, requestedType}); const args = createArgs({pageSize, pagination, requestedType}); const data = requestedType !== `user` ? await client.databases.query(args) : await client.users.list(args); const {results, next_cursor, has_more} = data; return { items: results.map((item) => processItem({account, schema, item})), "pagination": { "hasNext": has_more, "nextPageConfig": { start_cursor: next_cursor, schema: has_more ? schema : null, }, }, }; }; ``` Request example: ```json theme={null} { "filter": {}, "types": [ "f4642444-220c-439d-85d6-378ddff3d510", "3bd058e6-a71c-4e9a-8480-a76810ae38d3", "user" ], "requestedType": "3bd058e6-a71c-4e9a-8480-a76810ae38d3", "account": { "_id": "620a4396aec5dd672c4fed83", "access_token": "USER-TOKEN", "app": "620a3c9baec5dd25794fed7a", "auth": "oauth2", "owner": "620a3c46cf7154924cf442cb", "enabled": true, "name": "Fibery Developer", "masterAccountId": null, "lastUpdatedOn": "2022-02-21T13:30:51.350Z" }, "lastSynchronizedAt": null, "pagination": null } ``` Response example: ```json theme={null} { "items": [ { "id": "4455580b-000b-4313-8128-f1ca2d2dec34", "archived": false, "created_time": "2022-02-14T11:28:00.000Z", "last_edited_time": "2022-02-14T11:30:00.000Z", "__notion_link": "https://www.notion.so/Login-Page-4455580b000b43138128f1ca2d2dec34", "related to tasks (column)": [ "b829daf3-bae5-40a0-a090-56a30f240a28" ], "tags": [ "Urgent" ], "due date": "2022-02-24", "name": [ "Login Page" ] }, { "id": "9b3dff11-582b-498a-ba9b-571827ab3ca7", "archived": false, "created_time": "2022-02-14T11:28:00.000Z", "last_edited_time": "2022-02-14T11:29:00.000Z", "__notion_link": "https://www.notion.so/Home-Page-9b3dff11582b498aba9b571827ab3ca7", "related to tasks (column)": [ "987a714b-0b7e-4b03-bdaf-c0efc5d522fb", "539a4d0e-6871-434b-a5cb-619f5bd5a911" ], "tags": [ "Important", "Urgent" ], "due date": "2022-02-14", "name": [ "Home Page" ] } ], "pagination": { "hasNext": false, "nextPageConfig": { "start_cursor": null, "schema": null } } } ``` ## Source Code The source code of Notion integration can be found in [our public repository](https://gitlab.com/fibery-community/notion-app) as well as other examples. Notion app is used in production and can be tried by following integrate link in your database editor. # Tutorial: Simple app Source: https://developers.fibery.com/guides/integrations/tutorial-simple-app Build a minimal custom integration app from scratch. In the tutorial we will implement the simplest app with token authentication. We will use NodeJS and Express framework, but you can use any other programming language. Find source code repository here: [https://gitlab.com/fibery-community/integration-sample-apps](https://gitlab.com/fibery-community/integration-sample-apps) To create simple app you need to implement following [Custom App: REST Endpoints](/guides/integrations/rest-endpoints): * getting app information: `GET /` * validate account: `POST /validate` * getting synchronizer configuration: `POST /api/v1/synchronizer/config` * getting schema: `POST /api/v1/synchronizer/schema` * fetching data `POST /api/v1/synchronizer/data` Let's implement them one by one. But first let's define dataset: ```javascript theme={null} const users = { token1: { name: `Dad Sholler`, data: { flower: [ {id: `47fd45cf-5a07-40aa-9ee4-a4258832154a`, name: `Rose`}, {id: `4f3afc75-2fb9-4ff8-b26f-ab4bf2f470a3`, name: `Lily`}, {id: `d7525fc1-1979-4cfb-ba32-0dfab9280b24`, name: `Tulip`}, ], regionPrice: [ { id: `56c50696-1d9f-4e4d-9678-448017d25474`, flowerId: `d7525fc1-1979-4cfb-ba32-0dfab9280b24`, price: 10, name: `Eastern Europe`, }, { id: `503e5efb-7650-4c3a-85e9-f4ebc23adfd5`, name: `Western Europe`, flowerId: `d7525fc1-1979-4cfb-ba32-0dfab9280b24`, price: 15, }, { id: `87ccd5ef-ed3f-49db-9bcc-c9d1daf91744`, name: `Eastern Europe`, price: 20, flowerId: `47fd45cf-5a07-40aa-9ee4-a4258832154a`, }, ], }, }, token2: { name: `Ben Dreamer`, data: { flower: [ {id: `4f3afc75-2fb9-4ff8-b26f-ab4bf2f470a3`, name: `Lily`}, {id: `d7525fc1-1979-4cfb-ba32-0dfab9280b24`, name: `Tulip`}, ], regionPrice: [ { id: `c3352e8c-e62c-4e26-9a8b-852c1a3d2435`, flowerId: `d7525fc1-1979-4cfb-ba32-0dfab9280b24`, price: 10, name: `East Coast`, }, { id: `7a581588-d5fc-46aa-a084-8e2b28f3d6e5`, name: `West Coast`, flowerId: `d7525fc1-1979-4cfb-ba32-0dfab9280b24`, price: 15, }, { id: `6e510195-41e3-499b-9a76-9ad928720882`, name: `Asia`, price: 20, flowerId: `4f3afc75-2fb9-4ff8-b26f-ab4bf2f470a3`, }, ], }, }, }; ``` ## Getting app information App information should have the structure described in [App configuration](/guides/integrations/app-configuration). Let's implement it. We can see that the app require token authentication and responsible for data synchronization. ```javascript theme={null} app.get(`/`, (req, res) => { res.json({ id: 'integration-sample-app', name: 'Integration Sample App', version: `1.0.0`, type: 'crunch', description: 'Integration sample app.', authentication: [ { description: 'Provide Token', name: 'Token Authentication', id: 'token', fields: [ { type: 'text', description: 'Personal Token', id: 'token', }, ], }, ], sources: [], responsibleFor: { dataSynchronization: true, }, }); }); ``` ## **Validate account** Since authentication is required we should run an authentication and return corresponding user name. You can find more information about the endpoint here. ```javascript theme={null} app.post(`/validate`, (req, res) => { const user = users[req.body.fields.token]; if (user) { return res.json({name: user.name}); } res.status(401).json({message: `Unauthorized`}); }); ``` ## **Getting sync configuration** This is basic a scenario and we don't need any dynamic. So we will return a static configuration with no filters. ```javascript theme={null} app.post(`/api/v1/synchronizer/config`, (req, res) => { res.json({ types: [ {id: `flower`, name: `Flower`}, {id: `regionPrice`, name: `Region Price`}, ], filters: [], }); }); ``` ## Getting schema We should provide schema for selected types. ```javascript theme={null} const schema = { flower: { id: {name: `Id`, type: `id`}, name: {name: `Name`, type: `text`}, }, regionPrice: { id: {name: `Id`, type: `id`}, name: {name: `Name`, type: `text`}, price: {name: `Price`, type: `number`}, flowerId: { name: `Flower Id`, type: `text`, relation: { cardinality: `many-to-one`, name: `Flower`, targetName: `Region Prices`, targetType: `flower`, targetFieldId: `id`, }, }, }, }; app.post(`/api/v1/synchronizer/schema`, (req, res) => { res.json( req.body.types.reduce((acc, type) => { acc[type] = schema[type]; return acc; }, {}), ); }); ``` ## **Fetching data** The endpoint receives requested type, accounts, filter and set of selected types and should return actual data. ```javascript theme={null} app.post(`/api/v1/synchronizer/data`, (req, res) => { const {requestedType, account} = req.body; return res.json({ items: users[account.token].data[requestedType], }); }); ``` And that's it! Our app is ready for use. See full example [here](https://gitlab.com/fibery-community/integration-sample-apps). # Webhooks Source: https://developers.fibery.com/guides/integrations/webhooks Receive real-time updates from external systems via webhooks. Most 3rd party systems allow reacting to updates almost immediately thanks to webhooks. Custom apps in Fibery can also take advantage of it and update the data immediately without the need to wait for a scheduled synchronization run Only Fibery Admins can configure webhooks. ## Connector Configuration To enable webhooks, first of all you need update connector's [synchronizer configuration](/guides/integrations/rest-endpoints#post-/api/v1/synchronizer/config) by specifying `webhooks` configuration: ```json theme={null} { "types": [...], "filters": [...], "webhooks": { "enabled": true, "type": "ui" } } ``` NOTE: `type: 'ui'` is the only supported type for now. Most of the 3rd party system allow configuring webhooks via their UI instead of API request, hence the name Then you should implement following URLs in the connector: * [install](/guides/integrations/rest-endpoints#post-/api/v1/synchronizer/webhooks) * [pre-process](/guides/integrations/rest-endpoints#post-/api/v1/synchronizer/webhooks/pre-process) * [data transformation](/guides/integrations/rest-endpoints#post-/api/v1/synchronizer/webhooks/transform) ## Accepting webhook events Each app has its own entry point `https://webhooks-svc.fibery.io/apps/:app-id` (e.g. [`https://webhooks-svc.fibery.io/apps/slack-app`](https://webhooks-svc.fibery.io/apps/:app-id)). This url should be registered in 3rd party system as a target url for incoming messages **How to get your app-id?** Unfortunately that's not easy for now. You'll have to use browser's devtools and get the id from the request in Fibery. Here's a video showing how you can get it