Data Feed API Guide


Overview of the API

Data Feeds provide a powerful way to store structured data, similar to a spreadsheet. This data can then be used in apps and compositions to add flexibility to your content.

The following is a comprehensive guide into all the features available through the API with samples along the way to help you build feature-rich integrations.

The API was designed to take advantage of all Data Feed capabilities, enabling you to dynamically create and update rows or columns through the updateDataFeed mutation.

The updateDataFeed mutation streamlines the processes of inserting, modifying, deleting rows and columns in a single API call via its UpdateDataFeedInput. To execute this mutation, you need to first make a Query type call to the API in order to retrieve the Data Feed ID, as well as the IDs of the rows and columns you intend to update or delete.

In order to specify the action you wish to take, you can use the rows or columns field, which can accept any of the objects provided by the UpdateDataFeedRowOps or UpdateDataFeedColumnOps object. These objects encompass actions such as create, update or delete. Additionally, there are actions like accept and reject which are used to either approve or reject pending row changes.

You can also create new Data Feeds using the createDataFeed mutation. This guide contains an example of creating Data Feeds below.


Retrieving Data Feeds plus their columns and rows

The Data Feed columns and rows can be queried using the dataFeed or dataFeeds connection on the Organization entity. To filter columns and rows as well as to know more about their fields, see the Data Feed connection.

Other connections on the API have a limit of up to 100 nodes per query, but given that Data Feeds can hold a lot of data, the limit has been increased to 1.000 columns and 5.000 rows per data feed in the same query. Those values match the limits of the Data Feed themselves, so you can always load a whole Data Feed without the need for pagination.
{
  organization {
    dataFeeds(first: 1) {
      nodes {
        id
        name
        columns(first: 100) {
          nodes {
            id
            kind
            name
          }
        }
        rows(first: 5000) {
          nodes {
            id
            values
          }
        }
      }
    }
  }
}

Running the above query will return data in the following format. Please mind that columns that contains media files, such as images or videos, return a special object containg information about the media. Please refer to the handling media files section of this guide for more information.

{
  "data": {
    "organization": {
      "dataFeeds": {
        "nodes": [
          {
            "id": "1qU8rGm",
            "columns": {
              "nodes": [
                {
                  "id": "col3psX",
                  "kind": "TEXT",
                  "name": "First Column"
                },
                {
                  "id": "coljAsE",
                  "kind": "IMAGE",
                  "name": "Second Column"
                }
              ]
            },
            "rows": {
              "nodes": [
                {
                  "id": "lJFv",
                  "values": {
                    "col3psX": "First Row",
                    "coljAsE": {
                      "mediaRef": "3e9b5212dfd.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE2OTg4ODMyMDAsInN1YiI6IjFxVThyR20ifQ.ZatNrmka9ldxJ6ayaA0aThUEhfiiNi4k-5TasmDESEA",
                      "kind": "IMAGE",
                      "name": "first-image.png",
                      "width": 1280,
                      "height": 720,
                      "uploadedAt": "2023-11-02T12:38:59Z"
                    }
                  }
                },
                {
                  "id": "4lFp8Da",
                  "values": {
                    "col3psX": "Second Row",
                    "coljAsE": {
                      "mediaRef": "5c21e9aa899.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE2OTg4ODMyMDAsInN1YiI6IjFxVThyR20ifQ.ZatNrmka9ldxJ6ayaA0aThUEhfiiNi4k-5TasmDESEA",
                      "kind": "IMAGE",
                      "name": "second-image.png",
                      "width": 2161,
                      "height": 1350,
                      "uploadedAt": "2023-11-02T18:52:28Z"
                    }
                  }
                },
                {
                  "id": "7qFxoAQ",
                  "values": {
                    "col3psX": "Third Row",
                    "coljAsE": {
                      "mediaRef": "bb35fa47c21.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE2OTg4ODMyMDAsInN1YiI6IjFxVThyR20ifQ.ZatNrmka9ldxJ6ayaA0aThUEhfiiNi4k-5TasmDESEA",
                      "kind": "IMAGE",
                      "name": "third-image.png",
                      "width": 667,
                      "height": 569,
                      "uploadedAt": "2023-11-02T20:55:59Z"
                    }
                  }
                }
              ]
            }
          }
        ]
      }
    }
  }
}

Creating Data Feeds

Through the createDataFeed mutation, you can create a new Data Feed with columns and rows. The mutation accepts the following arguments:

Arguments

Name Type Description
name string Required. The name of the Data Feed.
parentId string Optional. Folder ID where the Data Feed will be created. If not provided, the Data Feed will be created on the Organization root folder.
tags string[] Optional. The tags of the Data Feed, e.g. ["A", "B"]
columns ColumnOpCreate[] Optional. Inital columns for the Data Feed. See ColumnOpCreate.
rows RowOpInsert[] Optional. Inital rows for the Data Feed. See RowOpInsert.

The columns and rows fields are optional, but if you want to create a Data Feed with initial columns and rows, see columns ColumnOpCreate section, and for rows see RowOpInsert section.

To understand why you need an ID when creating columns, see the Create a column section in this page. Or see the example below, where we declare columns with temporary IDs, and reference them in row values field when creating the rows.
Examples

Create Data Feed with initial columns and rows

mutation {
  createDataFeed(input: {
    name: "Shine Data Feed"
    tags: ["A", "B"]
    columns: [
      {
        # We declare a temporary column ID to reference when inserting rows.
        # Temporary IDs must start with "new", be alphanumeric and
        # unique for this the mutation.
        id: "new0"
        name: "NAME"
        required: false
        kind: TEXT
      }
      {
        id: "new1"
        name: "AGE"
        required: false
        kind: INTEGER
      }
    ]
    rows: [
      {
        # Here we can use the temporary column IDs to insert rows.
        values: {
          new0: "NICE NAME HERE"
          new1: 23
        }
      }
      {
        values: {
          new0: "ANOTHER NAME HERE"
        }
      }
    ]
  }) {
    dataFeed {
      name
      id
      columns(first: 2) {
        nodes {
          # Here you will get the final Column ID
          id
          name
        }
      }
      rows(first: 2) {
        nodes {
          id
          # Values here will reference the final Column ID
          values
        }
      }
    }
  }
}

Columns

Create a column

To initiate column creation, utilize the create action field within the mutation's columns input. The create input object need an id and values.

The id is used to identify the column when creating or updating rows. As the column has not yet been created, it does not have an ID created by the server yet, so you must provide a temporary ID to be used throughout the operation.

Column ids are unique, so you must ensure that the id you provide is not already in use. All existing column ids of a Data Feed start with the prefix col. When providing a temporary id do not use this prefix. We recommend using temporary IDs that start with new as a prefix like so: new0, new1, new2....

In values field, you currently only need to provide the necessary values, these are name|kind|required, see more on ColumnOpCreate section.

Examples

Create multiples columns with one query

mutation {
  updateDataFeed(input: {
    dataFeedId: "deU4o8vQ"
    columns: {
      create: [
        {
          id: "new0"
          name: "NAME"
          required: false
          kind: TEXT
        }
        {
          id: "new1"
          name: "AGE"
          required: false
          kind: NUMBER
        }
      ]
    }
  }) {
    dataFeed {
      id
      name
    }
  }
}

Updating a column

To update a column, provide the column ID and the new values. Use the update action field within the column input field of the mutation. Each operation in the update array should include the id and values fields.

Examples

Update Data Feed columns

mutation {
  updateDataFeed(input: {
    dataFeedId: "deU4o8vQ"
    columns: {
      update: [
        {
          id: "collBs4v"
          name: "New Name"
          note: "Lorem ipsum dolor sit amet, consectetur adipiscing elit."
        }
      ]
    }
  }) {
    dataFeed {
      id
      name
    }
  }
}

Deleting a column

To delete a column, you only need to provide the column ID. Within the mutation's column input field, use the delete action. Format the values as JSON key/value pairs in the following manner: { id: columnId }. Ensure that the columnId corresponds to the ID of an existing column; otherwise, the API will return an error.

Examples
mutation {
  updateDataFeed(input: {
    dataFeedId: "deU4o8vQ"
    columns: {
      delete: [
        {
          id: "collBs4v"
        }
        {
          id: "coljAs4q"
        }
      ]
    }
  }) {
    dataFeed {
      id
      name
    }
  }
}

Rows

Inserting a new row

To insert a row only need to provide the required values. Utilize the insert action field within the mutation's rows input.

The row insert object have the values and pending attributes. When inserting a row, the default value for pending is false. Rows added with pending: true will be marked as pending, and will require an manual approval, or approval through the API using the approveDataFeedRows mutation or performing an approve action on the row.

The values field is a JSON key/value pairs must follow this format: { columnId: value }. Ensure that columnId corresponds to an existing column ID, as the API will return an error otherwise.

Rows are appended to the Data Feed if you don't provide an index value. If you provide an index then the row will be inserted at the given index. Data Feed indexes are 1-based, not 0-based. If the Data Feed already has a row at the given index it will be moved down.

If there are required columns on the Data Feed when creating a row, the value for those columns must be provided through the values JSON field.
Examples

Create multiple rows using a single mutation

mutation {
  updateDataFeed(input: {
    dataFeedId: "ppU0MgeN"
    rows: {
      insert: [
        {
          # This will be appended to the Data Feed.
          pending: true
          values: {
            colOEs4M: "TEXT"
            colXysMD: 9
          }
        }
        {
          # This will be inserted at the first index of the Data Feed.
          index: 1,
          values: {
            colOEs4M: "OTHER TEXT"
            colXysMD: 2
          }
        }
      ]
    }
  }) {
    dataFeed {
      id
      name
    }
  }
}

If you only need to append rows there is a simplified mutation that can help you:

mutation {
  appendDataFeedRows(input: {
    clientMutationId: "random-mutation-id"
    dataFeedId: "ppU0MgeN"
    pending: true
    values: [
      {
        colOEs4M: "TEXT"
        colXysMD: 9
      }
    ]
  }) {
    clientMutationId
  }
}

Updating a row

To modify a row, you must supply both the row ID and the new values for the columns. Use the update action field within the row input field of the mutation. Each operation within the update array should include the id, values and version fields, pending is optional and defaults to false. The version field is optional and it is used to ensure that the row has not been modified since it was retrieved, if this field is omitted the API will not do any check and will overwrite the row with the new values. See more in Handling Conflicts.

You don't need to update all columns. You can choose to update only a single cell by sending only the corresponding columnId in the values field. If you wish to clear a column you must send its value as null.

You must provide the ID of an existing row, and values field must provide the column ID and the new value is a JSON, ensure the value type is same as the column kind.

Examples

Update multiple rows with different values for each one

mutation {
  updateDataFeed(input: {
    dataFeedId: "deU4o8vQ"
    rows: {
      update: [
        {
          # Updating the whole row
          id: "OAFQa"
          version: 1698423308
          values: {
            colOJn5F: ["TAG"]
            colOEs4M: "TEXT"
            colXysMD: 9
          }
        }
        {
          # Updating only a single cell
          id: "VEFe0"
          values: {
            colOJn5F: ["TAG 1", "TAG 2"]
          }
        }
        {
          # Clearing the values of two cells in this row
          id: "VEFe0"
          values: {
            colOJn5F: null
            colXysMD: null
          }
        }
      ]
    }
  }) {
    dataFeed {
      id
      name
    }
  }
}

Update multiple rows to same values using our simplified mutation

mutation {
  updateDataFeedRows(input: {
    clientMutationId: "random-mutation-id"
    dataFeedId: "deU4o8vQ"
    rows: [
      {
        id: "OAFQa"
        version: 1698423308
      }
      {
        id: "VEFe0"
      }
    ]
    pending: true
    values: {
      colOEs4M: "TEXT"
      colXysMD: 9
    }
  }) {
    clientMutationId
  }
}

Deleting a row

For deleting a row, you currently only need the row ID. Utilize the delete operation field within the row input field of the mutation. Each operation within the delete array should include the id. The pending and version fields, are optional and defaults to false and null respectively. The version field is used to ensure that the row has not been modified since it was retrieved, if the this field is omitted the API will not do any check and will overwrite any row operation, see more in Handling Conflicts. Ensure that id corresponds to an existing row ID, as the API will return an error otherwise.

Examples
mutation {
  updateDataFeed(input: {
    dataFeedId: "deU4o8vQ"
    rows: {
      delete: [
        {
          id: "OAFQa"
          pending: true
        }
        {
          id: "VEFe0"
          version: 1698423308
        }
      ]
    }
  }) {
    dataFeed {
      id
      name
    }
  }
}

or using our simplified mutation:

mutation {
  removeDataFeedRows(input: {
    clientMutationId: "random-mutation-id"
    dataFeedId: "deU4o8vQ"
    pending: true
    rows: [
      {
        id: "OAFQa"
        version: 1698423308
      }
      {
        id: "VEFe0"
      }
    ]
  }) {
    clientMutationId
  }
}

Pending changes

On all Data Feed operations you can set the pending attribute to true to create a pending change. Default value for pending is false. Rows inserted or updated with pending set to true will be marked as pending, and will require an approval before being available to apps and compositions.

Each rows in a Data Feed can have differents status. The status can be P_INSERT, P_UPDATE, P_DELETE or APPROVED. Check DataFeedRowStatus to know more about each status.

Pending changes

When inserting, updating or deleting a row, you can set the pending attribute to true to mark the change as pending approval.

Examples
mutation {
  updateDataFeed(input: {
    dataFeedId: "deU4o8vQ"
    rows: {
      insert: [
        {
          # This will insert a row with status = P_INSERT
          pending: true
          values: {
            colOEs4M: "TEXT"
            colXysMD: 9
          }
        }
      ]
      update: [
        {
          id: "OAFQa"
          # This will insert a row with status = P_UPDATE
          pending: true
          version: 1698423308
          values: {
            colOEs4M: "TEXT"
          }
        }
      ]
      delete: [
        {
          id: "VEFe0"
          # Pending set to true will set the status of this row to P_DELETE
          # and it will be deleted when the change is approved.
          pending: true
        }
      ]
    }
  }) {
    dataFeed {
      id
      name
    }
  }
}

Approve changes

Rows with status P_INSERT, P_UPDATE or P_DELETE can be approved using the updateDataFeed or approveDataFeedRows mutation.

Examples
mutation {
  updateDataFeed(input: {
    dataFeedId: "deU4o8vQ"
    rows: {
      approve: [
        {
          id: "OAFQa"
          version: 1698423308
        }
        {
          id: "VEFe0"
        }
      ]
    }
  }) {
    dataFeed {
      name
    }
  }
}

or using the simplified mutation

mutation {
  approveDataFeedRows(input: {
    dataFeedId: "deU4o8vQ"
    rows: [
      {
        id: "OAFQa"
        version: 1698423308
      }
      {
        id: "VEFe0"
      }
    ]
  }) {
    dataFeed {
      id
      name
    }
  }
}

Reject changes

Rows with any status except APPROVED can be rejected using the updateDataFeed or rejectDataFeedRows mutation.

Examples
mutation {
  updateDataFeed(input: {
    dataFeedId: "deU4o8vQ"
    rows: {
      reject: [
        {
          id: "OAFQa"
          version: 1698423308
        }
        {
          id: "VEFe0"
        }
      ]
    }
  }) {
    dataFeed {
      name
    }
  }
}

or using the simplified mutation:

mutation {
  rejectRowsDataFeed(input: {
    dataFeedId: "deU4o8vQ"
    rows: [
      {
        id: "OAFQa"
        version: 1698423308
      }
      {
        id: "VEFe0"
      }
    ]
  }) {
    dataFeed {
      id
      name
    }
  }
}

Mixing column and row creation

You can mix column and row creation in a single mutation. With all the knowledge above, you can mix all operations learned from updateDataFeed mutation and make all changes using just one request.

Examples
mutation {
  updateDataFeed(input: {
    dataFeedId: "ppU0MgeN"
    columns: {
      create: [
        {
          id: "new0"
          name: "NAME"
          required: false
          kind: TEXT
        }
        {
          id: "new1"
          name: "AGE"
          required: true
          kind: NUMBER
        }
      ]
    }
    rows: {
      update: [
        {
          id: "hjKT4s"
          version: 1698423308
          values: {
            new1: 45
            colXysMD: "THIS COLUMN ALREADY EXISTS"
          }
        }
      ]
      insert: [
        {
          pending: true
          values: {
            new0: "TEXT"
            new1: 9
          }
        }
        {
          values: {
            new0: "SOME TEXT"
            new1: 2
            colXysMD: "THIS COLUMN ALREADY EXISTS"
          }
        }
      ]
    }
  }) {
    dataFeed {
      id
      name
    }
  }
}

Handling Conflicts

Whenever a Data Feed row is inserted or updated, it inherits its version from the current Data Feed (the Data Feed version also changes when something is updated). This version is used to carry out checks when the Data Feed is being updated, more precisely to avoid situations where when you update a piece of data and this data has already changed since the last time you collected the information.

So every time you update the values ​​of a row, delete a row, approve or reject changes waiting for approval, you can provide the row's version field. This field is optional, if omitted, the version check will be ignored and will overwrite whatever is in the Data Feed, regardless of its row status or version.

If you provide the version field, the API will check if the version you provided matches the current version of the row. If the versions do not match, the API will return an error and fails the mutation, and you will need to retrieve the Data Feed rows again to get the latest version of the row and try again. Or remove the version field from the mutation to ignore the version check.

The row version can be obtained by querying the Data Feed rows, see DataFeedRow section.

See UpdateDataFeedRowOps to know where to provide the version field. You can provide the version field in the update, delete, approve and reject operations. insert operation does not have a version field, because it will always insert a new row, so there is no risk of overwriting data. Don't forget to see the examples of mutations in the sections above, they show situations with the version being provided.


Handling Media Columns and Rows

Columns with kind such as IMAGE, VIDEO or MEDIA return an object containing information about the media they hold. MEDIA columns can contain any kind of media, so the returned value also has a kind field to allow you to know whether the uploaded file was an image or a video.

Key Type Description
mediaRef string Contains a value that can be used to update other rows to the same media file, regardless whether it is in the same data feed or a different one. The mediaRef is valid for use within 7 days. This value should not be stored and cannot be used for comparison because it changes every 7 days.
kind string The kind of this media. One of "IMAGE" or "VIDEO"
name string The name of the file being uploaded and displayed in the Data Feed. Like "media.png" or "video.mp4".
width integer The width of this media, in pixels.
height integer The height of this media, in pixels.
duration float Optional. If kind is "VIDEO" then this will be duration of the video in seconds.
uploadedAt string The UTC date and time this media was uploaded to the Data Feed, in ISO 8601 format. Like this: "2023-11-02T12:38:59Z".

Here is how Data Feed media is returned from a query:

{
  "data": {
    "organization": {
      "dataFeed": {
        "rows": {
          "nodes": [
            {
              "values": {
                "col3psX": "First Row",
                "coljAsE": {
                  "mediaRef": "3e9b5212dfd.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE2OTg4ODMyMDAsInN1YiI6IjFxVThyR20ifQ.ZatNrmka9ldxJ6ayaA0aThUEhfiiNi4k-5TasmDESEA",
                  "kind": "IMAGE",
                  "name": "image-file.png",
                  "width": 1280,
                  "height": 720,
                  "uploadedAt": "2023-11-020T12:38:59Z"
                },
                "colnYs7Dv": {
                  "mediaRef": "1e72659edf6.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE2OTg4ODMyMDAsInN1YiI6IjFxVThyR20ifQ.ZatNrmka9ldxJ6ayaA0aThUEhfiiNi4k-5TasmDESEA",
                  "kind": "VIDEO",
                  "name": "video-file.mp4",
                  "width": 624,
                  "height": 352,
                  "uploadedAt": "2023-11-02T16:19:50Z",
                  "duration": 30.030022
                }
              }
            }
          ]
        }
      }
    }
  }
}

Each media cell contains a mediaRef value that can be used to download the file through HTTP or use the same file to populate other cells. The mediaRef is valid for use within 7 days.

To download a media file all you have to do is add the mediaRef to this URL:

https://api.onsign.tv/df-media/

Like this:

$ curl https://api.onsign.tv/df-media/1e72659edf6.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE2OTg4ODMyMDAsInN1YiI6IjFxVThyR20ifQ.ZatNrmka9ldxJ6ayaA0aThUEhfiiNi4k-5TasmDESEA

No authentication is required to download because the mediaRef already contains an authentication token that allow access to the media for 7 days.

Here is how you can use a mediaRef for updating or inserting rows in a Data Feed:

mutation {
  updateDataFeed(input: {
    dataFeedId: "deU4o8vQ"
    rows: {
      update: [
        {
          id: "OAFQa"
          # You can use the mediaRef directly in the column.
          values: {
            colZGs06: "3e9b5212dfd.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE2OTg4ODMyMDAsInN1YiI6IjFxVThyR20ifQ.ZatNrmka9ldxJ6ayaA0aThUEhfiiNi4k-5TasmDESEA"
          }
        }
      ],
      insert: [
        {
          # Or the mediaRef can be wrapped in an object.
          values: {
            colZGs06: {
              mediaRef: "3e9b5212dfd.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE2OTg4ODMyMDAsInN1YiI6IjFxVThyR20ifQ.ZatNrmka9ldxJ6ayaA0aThUEhfiiNi4k-5TasmDESEA"
            }
          }
        }
      ]
    }
  }) {
    dataFeed {
      id
      name
    }
  }
}

Uploading Media Files

Using a new image, video or media content in your a Data Feed is two-step process.

First you upload it through a HTTP POST request to the following endpoint:

https://api.onsign.tv/upload-df-media

When making the upload request, ensure that you use an API Token with the content:write permission in the Authorization header, using the value Token <api_token>. Also, remember to include the Accept: application/json header.

The upload endpoint accepts the following GET parameters:

Name Type Description
name string Optional. The name of the file being uploaded and displayed in the Data Feed. If not provided, the name will be set dinamically based on the file type. E.g. media.png, video.mp4.
Example
$ curl -X POST -H "Accept: application/json" \
  -H "Authorization: token YOUR-TOKEN" \
  --data-binary "@file.png" \
  https://api.onsign.tv/upload-df-media?name=media.png

Sample response (JSON):

{
  "mediaRef": "3e9b5212dfd.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE2OTg4ODMyMDAsInN1YiI6IjFxVThyR20ifQ.ZatNrmka9ldxJ6ayaA0aThUEhfiiNi4k-5TasmDESEA"
}

After calling the Data Feed upload endpoint, you'll receive a mediaRef which will can used when updating media of a Data Feed cell.

Please mind that the returned mediaRef is temporary and can be used for up to 7 days.
After using it on a mutation the value retrieved from the Data Feed cell will be different.

Overview