Repository: hyperledger/firefly
Pull request #1034 (2022-09-01 17:05:42)

Routes to directly publish blob/JSON data to shared storage (IFPS etc.)
A small extension to external API routes, that let you initiate uploads through your public storage plugin directly.

Unlike with the full on-chain/off-chain orchestration capabilities, no modification is made to the data, and no batching happens.

So this is the feature you would use for example to publish the metadata+binary of an NFT.

codecov-commenter: # [Codecov](https://codecov.io/gh/hyperledger/firefly/pull/1034?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hyperledger) Report
> Merging [#1034](https://codecov.io/gh/hyperledger/firefly/pull/1034?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hyperledger) (18e5495) into [main](https://codecov.io/gh/hyperledger/firefly/commit/4ccefb2cd05fb6983a1638ea5fb3239e2800af63?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hyperledger) (4ccefb2) will **decrease** coverage by `0.00%`.
> The diff coverage is `98.16%`.

```diff
@@            Coverage Diff             @@
##             main    #1034      +/-   ##
==========================================
- Coverage   99.98%   99.98%   -0.01%     
==========================================
  Files         302      305       +3     
  Lines       19961    20055      +94     
==========================================
+ Hits        19959    20051      +92     
- Misses          2        3       +1     
- Partials        0        1       +1     
```


| [Impacted Files](https://codecov.io/gh/hyperledger/firefly/pull/1034?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hyperledger) | Coverage Δ | |
|---|---|---|
| [internal/apiserver/routes.go](https://codecov.io/gh/hyperledger/firefly/pull/1034/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hyperledger#diff-aW50ZXJuYWwvYXBpc2VydmVyL3JvdXRlcy5nbw==) | `100.00% <ø> (ø)` | |
| [pkg/core/data.go](https://codecov.io/gh/hyperledger/firefly/pull/1034/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hyperledger#diff-cGtnL2NvcmUvZGF0YS5nbw==) | `100.00% <ø> (ø)` | |
| [pkg/core/operation.go](https://codecov.io/gh/hyperledger/firefly/pull/1034/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hyperledger#diff-cGtnL2NvcmUvb3BlcmF0aW9uLmdv) | `100.00% <ø> (ø)` | |
| [internal/apiserver/route\_get\_data\_value.go](https://codecov.io/gh/hyperledger/firefly/pull/1034/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hyperledger#diff-aW50ZXJuYWwvYXBpc2VydmVyL3JvdXRlX2dldF9kYXRhX3ZhbHVlLmdv) | `66.66% <66.66%> (ø)` | |
| [internal/apiserver/route\_post\_data\_blob\_publish.go](https://codecov.io/gh/hyperledger/firefly/pull/1034/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hyperledger#diff-aW50ZXJuYWwvYXBpc2VydmVyL3JvdXRlX3Bvc3RfZGF0YV9ibG9iX3B1Ymxpc2guZ28=) | `100.00% <100.00%> (ø)` | |
| [...nternal/apiserver/route\_post\_data\_value\_publish.go](https://codecov.io/gh/hyperledger/firefly/pull/1034/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hyperledger#diff-aW50ZXJuYWwvYXBpc2VydmVyL3JvdXRlX3Bvc3RfZGF0YV92YWx1ZV9wdWJsaXNoLmdv) | `100.00% <100.00%> (ø)` | |
| [internal/broadcast/manager.go](https://codecov.io/gh/hyperledger/firefly/pull/1034/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hyperledger#diff-aW50ZXJuYWwvYnJvYWRjYXN0L21hbmFnZXIuZ28=) | `100.00% <100.00%> (ø)` | |
| [internal/broadcast/operations.go](https://codecov.io/gh/hyperledger/firefly/pull/1034/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hyperledger#diff-aW50ZXJuYWwvYnJvYWRjYXN0L29wZXJhdGlvbnMuZ28=) | `100.00% <100.00%> (ø)` | |
| [internal/database/sqlcommon/data\_sql.go](https://codecov.io/gh/hyperledger/firefly/pull/1034/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hyperledger#diff-aW50ZXJuYWwvZGF0YWJhc2Uvc3FsY29tbW9uL2RhdGFfc3FsLmdv) | `100.00% <100.00%> (ø)` | |

Help us with your feedback. Take ten seconds to tell us [how you rate us](https://about.codecov.io/nps?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hyperledger). Have a feature suggestion? [Share it here.](https://app.codecov.io/gh/feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hyperledger)


Patch:
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 88f78eb56..10cd5a2c6 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -16,6 +16,7 @@
     "Correlator",
     "dataexchange",
     "datahash",
+    "dataid",
     "Debugf",
     "Deps",
     "devdocs",
diff --git a/db/migrations/postgres/000098_alter_data_public_ref.down.sql b/db/migrations/postgres/000098_alter_data_public_ref.down.sql
new file mode 100644
index 000000000..c8bd2aded
--- /dev/null
+++ b/db/migrations/postgres/000098_alter_data_public_ref.down.sql
@@ -0,0 +1,5 @@
+BEGIN;
+
+ALTER TABLE data DROP COLUMN public;
+
+COMMIT;
diff --git a/db/migrations/postgres/000098_alter_data_public_ref.up.sql b/db/migrations/postgres/000098_alter_data_public_ref.up.sql
new file mode 100644
index 000000000..50682f88e
--- /dev/null
+++ b/db/migrations/postgres/000098_alter_data_public_ref.up.sql
@@ -0,0 +1,6 @@
+BEGIN;
+
+ALTER TABLE data ADD COLUMN public VARCHAR(1024);
+UPDATE data SET public = '';
+
+COMMIT;
diff --git a/db/migrations/sqlite/000098_alter_data_public_ref.down.sql b/db/migrations/sqlite/000098_alter_data_public_ref.down.sql
new file mode 100644
index 000000000..e64f9af6d
--- /dev/null
+++ b/db/migrations/sqlite/000098_alter_data_public_ref.down.sql
@@ -0,0 +1,1 @@
+ALTER TABLE data DROP COLUMN public;
diff --git a/db/migrations/sqlite/000098_alter_data_public_ref.up.sql b/db/migrations/sqlite/000098_alter_data_public_ref.up.sql
new file mode 100644
index 000000000..439921c28
--- /dev/null
+++ b/db/migrations/sqlite/000098_alter_data_public_ref.up.sql
@@ -0,0 +1,2 @@
+ALTER TABLE data ADD COLUMN public VARCHAR(1024);
+UPDATE data SET public = '';
\ No newline at end of file
diff --git a/docs/reference/types/data.md b/docs/reference/types/data.md
index 8a8cf4e0f..37d386ee9 100644
--- a/docs/reference/types/data.md
+++ b/docs/reference/types/data.md
@@ -59,6 +59,7 @@ nav_order: 16
 | `created` | The creation time of the data resource | [`FFTime`](simpletypes#fftime) |
 | `datatype` | The optional datatype to use of validation of this data | [`DatatypeRef`](#datatyperef) |
 | `value` | The value for the data, stored in the FireFly core database. Can be any JSON type - object, array, string, number or boolean. Can be combined with a binary blob attachment | [`JSONAny`](simpletypes#jsonany) |
+| `public` | If the JSON value has been published to shared storage, this field is the id of the data in the shared storage plugin (IPFS hash etc.) | `string` |
 | `blob` | An optional hash reference to a binary blob attachment | [`BlobRef`](#blobref) |
 
 ## DatatypeRef
@@ -76,6 +77,6 @@ nav_order: 16
 | `hash` | The hash of the binary blob data | `Bytes32` |
 | `size` | The size of the binary data | `int64` |
 | `name` | The name field from the metadata attached to the blob, commonly used as a path/filename, and indexed for search | `string` |
-| `public` | If this data has been published to shared storage, this field is the id of the data in the shared storage plugin (IPFS hash etc.) | `string` |
+| `public` | If the blob data has been published to shared storage, this field is the id of the data in the shared storage plugin (IPFS hash etc.) | `string` |
 
 
diff --git a/docs/reference/types/message.md b/docs/reference/types/message.md
index 1e2c1c503..cfd55a5ce 100644
--- a/docs/reference/types/message.md
+++ b/docs/reference/types/message.md
@@ -72,7 +72,7 @@ nav_order: 15
 | `id` | The UUID of the message. Unique to each message | [`UUID`](simpletypes#uuid) |
 | `cid` | The correlation ID of the message. Set this when a message is a response to another message | [`UUID`](simpletypes#uuid) |
 | `type` | The type of the message | `FFEnum`:<br/>`"definition"`<br/>`"broadcast"`<br/>`"private"`<br/>`"groupinit"`<br/>`"transfer_broadcast"`<br/>`"transfer_private"` |
-| `txtype` | The type of transaction used to order/deliver this message | `FFEnum`:<br/>`"none"`<br/>`"unpinned"`<br/>`"batch_pin"`<br/>`"network_action"`<br/>`"token_pool"`<br/>`"token_transfer"`<br/>`"contract_invoke"`<br/>`"token_approval"` |
+| `txtype` | The type of transaction used to order/deliver this message | `FFEnum`:<br/>`"none"`<br/>`"unpinned"`<br/>`"batch_pin"`<br/>`"network_action"`<br/>`"token_pool"`<br/>`"token_transfer"`<br/>`"contract_invoke"`<br/>`"token_approval"`<br/>`"data_publish"` |
 | `author` | The DID of identity of the submitter | `string` |
 | `key` | The on-chain signing key used to sign the transaction | `string` |
 | `created` | The creation time of the message | [`FFTime`](simpletypes#fftime) |
diff --git a/docs/reference/types/operation.md b/docs/reference/types/operation.md
index fb8890b02..c182b7c7a 100644
--- a/docs/reference/types/operation.md
+++ b/docs/reference/types/operation.md
@@ -47,7 +47,7 @@ nav_order: 7
 | `id` | The UUID of the operation | [`UUID`](simpletypes#uuid) |
 | `namespace` | The namespace of the operation | `string` |
 | `tx` | The UUID of the FireFly transaction the operation is part of | [`UUID`](simpletypes#uuid) |
-| `type` | The type of the operation | `FFEnum`:<br/>`"blockchain_pin_batch"`<br/>`"blockchain_network_action"`<br/>`"blockchain_invoke"`<br/>`"sharedstorage_upload_batch"`<br/>`"sharedstorage_upload_blob"`<br/>`"sharedstorage_download_batch"`<br/>`"sharedstorage_download_blob"`<br/>`"dataexchange_send_batch"`<br/>`"dataexchange_send_blob"`<br/>`"token_create_pool"`<br/>`"token_activate_pool"`<br/>`"token_transfer"`<br/>`"token_approval"` |
+| `type` | The type of the operation | `FFEnum`:<br/>`"blockchain_pin_batch"`<br/>`"blockchain_network_action"`<br/>`"blockchain_invoke"`<br/>`"sharedstorage_upload_batch"`<br/>`"sharedstorage_upload_blob"`<br/>`"sharedstorage_upload_value"`<br/>`"sharedstorage_download_batch"`<br/>`"sharedstorage_download_blob"`<br/>`"dataexchange_send_batch"`<br/>`"dataexchange_send_blob"`<br/>`"token_create_pool"`<br/>`"token_activate_pool"`<br/>`"token_transfer"`<br/>`"token_approval"` |
 | `status` | The current status of the operation | `OpStatus` |
 | `plugin` | The plugin responsible for performing the operation | `string` |
 | `input` | The input to this operation | [`JSONObject`](simpletypes#jsonobject) |
diff --git a/docs/reference/types/transaction.md b/docs/reference/types/transaction.md
index f526c2539..d7a20531b 100644
--- a/docs/reference/types/transaction.md
+++ b/docs/reference/types/transaction.md
@@ -40,7 +40,7 @@ nav_order: 6
 |------------|-------------|------|
 | `id` | The UUID of the FireFly transaction | [`UUID`](simpletypes#uuid) |
 | `namespace` | The namespace of the FireFly transaction | `string` |
-| `type` | The type of the FireFly transaction | `FFEnum`:<br/>`"none"`<br/>`"unpinned"`<br/>`"batch_pin"`<br/>`"network_action"`<br/>`"token_pool"`<br/>`"token_transfer"`<br/>`"contract_invoke"`<br/>`"token_approval"` |
+| `type` | The type of the FireFly transaction | `FFEnum`:<br/>`"none"`<br/>`"unpinned"`<br/>`"batch_pin"`<br/>`"network_action"`<br/>`"token_pool"`<br/>`"token_transfer"`<br/>`"contract_invoke"`<br/>`"token_approval"`<br/>`"data_publish"` |
 | `created` | The time the transaction was created on this node. Note the transaction is individually created with the same UUID on each participant in the FireFly transaction | [`FFTime`](simpletypes#fftime) |
 | `blockchainIds` | The blockchain transaction ID, in the format specific to the blockchain involved in the transaction. Not all FireFly transactions include a blockchain. FireFly transactions are extensible to support multiple blockchain transactions | `string[]` |
 
diff --git a/docs/swagger/swagger.yaml b/docs/swagger/swagger.yaml
index df14e778b..4d0d243a2 100644
--- a/docs/swagger/swagger.yaml
+++ b/docs/swagger/swagger.yaml
@@ -676,6 +676,7 @@ paths:
                     - blockchain_invoke
                     - sharedstorage_upload_batch
                     - sharedstorage_upload_blob
+                    - sharedstorage_upload_value
                     - sharedstorage_download_batch
                     - sharedstorage_download_blob
                     - dataexchange_send_batch
@@ -748,6 +749,7 @@ paths:
                     - blockchain_invoke
                     - sharedstorage_upload_batch
                     - sharedstorage_upload_blob
+                    - sharedstorage_upload_value
                     - sharedstorage_download_batch
                     - sharedstorage_download_blob
                     - dataexchange_send_batch
@@ -3306,6 +3308,7 @@ paths:
                     - blockchain_invoke
                     - sharedstorage_upload_batch
                     - sharedstorage_upload_blob
+                    - sharedstorage_upload_value
                     - sharedstorage_download_batch
                     - sharedstorage_download_blob
                     - dataexchange_send_batch
@@ -3378,6 +3381,7 @@ paths:
                     - blockchain_invoke
                     - sharedstorage_upload_batch
                     - sharedstorage_upload_blob
+                    - sharedstorage_upload_value
                     - sharedstorage_download_batch
                     - sharedstorage_download_blob
                     - dataexchange_send_batch
@@ -4150,6 +4154,11 @@ paths:
         name: id
         schema:
           type: string
+      - description: 'Data filter field. Prefixes supported: > >= < <= @ ^ ! !@ !^'
+        in: query
+        name: public
+        schema:
+          type: string
       - description: 'Data filter field. Prefixes supported: > >= < <= @ ^ ! !@ !^'
         in: query
         name: validator
@@ -4214,9 +4223,9 @@ paths:
                             for search
                           type: string
                         public:
-                          description: If this data has been published to shared storage,
-                            this field is the id of the data in the shared storage
-                            plugin (IPFS hash etc.)
+                          description: If the blob data has been published to shared
+                            storage, this field is the id of the data in the shared
+                            storage plugin (IPFS hash etc.)
                           type: string
                         size:
                           description: The size of the binary data
@@ -4251,6 +4260,11 @@ paths:
                     namespace:
                       description: The namespace of the data resource
                       type: string
+                    public:
+                      description: If the JSON value has been published to shared
+                        storage, this field is the id of the data in the shared storage
+                        plugin (IPFS hash etc.)
+                      type: string
                     validator:
                       description: The data validator type
                       type: string
@@ -4345,9 +4359,9 @@ paths:
                           for search
                         type: string
                       public:
-                        description: If this data has been published to shared storage,
-                          this field is the id of the data in the shared storage plugin
-                          (IPFS hash etc.)
+                        description: If the blob data has been published to shared
+                          storage, this field is the id of the data in the shared
+                          storage plugin (IPFS hash etc.)
                         type: string
                       size:
                         description: The size of the binary data
@@ -4382,6 +4396,11 @@ paths:
                   namespace:
                     description: The namespace of the data resource
                     type: string
+                  public:
+                    description: If the JSON value has been published to shared storage,
+                      this field is the id of the data in the shared storage plugin
+                      (IPFS hash etc.)
+                    type: string
                   validator:
                     description: The data validator type
                     type: string
@@ -4432,9 +4451,9 @@ paths:
                           for search
                         type: string
                       public:
-                        description: If this data has been published to shared storage,
-                          this field is the id of the data in the shared storage plugin
-                          (IPFS hash etc.)
+                        description: If the blob data has been published to shared
+                          storage, this field is the id of the data in the shared
+                          storage plugin (IPFS hash etc.)
                         type: string
                       size:
                         description: The size of the binary data
@@ -4469,6 +4488,11 @@ paths:
                   namespace:
                     description: The namespace of the data resource
                     type: string
+                  public:
+                    description: If the JSON value has been published to shared storage,
+                      this field is the id of the data in the shared storage plugin
+                      (IPFS hash etc.)
+                    type: string
                   validator:
                     description: The data validator type
                     type: string
@@ -4626,6 +4650,105 @@ paths:
           description: ""
       tags:
       - Default Namespace
+  /data/{dataid}/blob/publish:
+    post:
+      description: Publishes the binary blob attachment stored in your local data
+        exchange, to shared storage
+      operationId: postDataBlobPublish
+      parameters:
+      - description: The blob ID
+        in: path
+        name: dataid
+        required: true
+        schema:
+          type: string
+      - description: Server-side request timeout (milliseconds, or set a custom suffix
+          like 10s)
+        in: header
+        name: Request-Timeout
+        schema:
+          default: 2m0s
+          type: string
+      requestBody:
+        content:
+          application/json:
+            schema:
+              additionalProperties: {}
+              type: object
+      responses:
+        "200":
+          content:
+            application/json:
+              schema:
+                properties:
+                  blob:
+                    description: An optional hash reference to a binary blob attachment
+                    properties:
+                      hash:
+                        description: The hash of the binary blob data
+                        format: byte
+                        type: string
+                      name:
+                        description: The name field from the metadata attached to
+                          the blob, commonly used as a path/filename, and indexed
+                          for search
+                        type: string
+                      public:
+                        description: If the blob data has been published to shared
+                          storage, this field is the id of the data in the shared
+                          storage plugin (IPFS hash etc.)
+                        type: string
+                      size:
+                        description: The size of the binary data
+                        format: int64
+                        type: integer
+                    type: object
+                  created:
+                    description: The creation time of the data resource
+                    format: date-time
+                    type: string
+                  datatype:
+                    description: The optional datatype to use of validation of this
+                      data
+                    properties:
+                      name:
+                        description: The name of the datatype
+                        type: string
+                      version:
+                        description: The version of the datatype. Semantic versioning
+                          is encouraged, such as v1.0.1
+                        type: string
+                    type: object
+                  hash:
+                    description: The hash of the data resource. Derived from the value
+                      and the hash of any binary blob attachment
+                    format: byte
+                    type: string
+                  id:
+                    description: The UUID of the data resource
+                    format: uuid
+                    type: string
+                  namespace:
+                    description: The namespace of the data resource
+                    type: string
+                  public:
+                    description: If the JSON value has been published to shared storage,
+                      this field is the id of the data in the shared storage plugin
+                      (IPFS hash etc.)
+                    type: string
+                  validator:
+                    description: The data validator type
+                    type: string
+                  value:
+                    description: The value for the data, stored in the FireFly core
+                      database. Can be any JSON type - object, array, string, number
+                      or boolean. Can be combined with a binary blob attachment
+                type: object
+          description: Success
+        default:
+          description: ""
+      tags:
+      - Default Namespace
   /data/{dataid}/messages:
     get:
       description: Gets a list of the messages associated with a data item
@@ -4857,6 +4980,7 @@ paths:
                         - token_transfer
                         - contract_invoke
                         - token_approval
+                        - data_publish
                         type: string
                       type:
                         description: The type of the message
@@ -4896,6 +5020,250 @@ paths:
           description: ""
       tags:
       - Default Namespace
+  /data/{dataid}/value:
+    get:
+      description: Downloads the JSON value of the data resource, without the associated
+        metadata
+      operationId: getDataValue
+      parameters:
+      - description: The blob ID
+        in: path
+        name: dataid
+        required: true
+        schema:
+          type: string
+      - description: Server-side request timeout (milliseconds, or set a custom suffix
+          like 10s)
+        in: header
+        name: Request-Timeout
+        schema:
+          default: 2m0s
+          type: string
+      - description: 'Data filter field. Prefixes supported: > >= < <= @ ^ ! !@ !^'
+        in: query
+        name: author
+        schema:
+          type: string
+      - description: 'Data filter field. Prefixes supported: > >= < <= @ ^ ! !@ !^'
+        in: query
+        name: batch
+        schema:
+          type: string
+      - description: 'Data filter field. Prefixes supported: > >= < <= @ ^ ! !@ !^'
+        in: query
+        name: cid
+        schema:
+          type: string
+      - description: 'Data filter field. Prefixes supported: > >= < <= @ ^ ! !@ !^'
+        in: query
+        name: confirmed
+        schema:
+          type: string
+      - description: 'Data filter field. Prefixes supported: > >= < <= @ ^ ! !@ !^'
+        in: query
+        name: created
+        schema:
+          type: string
+      - description: 'Data filter field. Prefixes supported: > >= < <= @ ^ ! !@ !^'
+        in: query
+        name: group
+        schema:
+          type: string
+      - description: 'Data filter field. Prefixes supported: > >= < <= @ ^ ! !@ !^'
+        in: query
+        name: hash
+        schema:
+          type: string
+      - description: 'Data filter field. Prefixes supported: > >= < <= @ ^ ! !@ !^'
+        in: query
+        name: id
+        schema:
+          type: string
+      - description: 'Data filter field. Prefixes supported: > >= < <= @ ^ ! !@ !^'
+        in: query
+        name: key
+        schema:
+          type: string
+      - description: 'Data filter field. Prefixes supported: > >= < <= @ ^ ! !@ !^'
+        in: query
+        name: pins
+        schema:
+          type: string
+      - description: 'Data filter field. Prefixes supported: > >= < <= @ ^ ! !@ !^'
+        in: query
+        name: sequence
+        schema:
+          type: string
+      - description: 'Data filter field. Prefixes supported: > >= < <= @ ^ ! !@ !^'
+        in: query
+        name: state
+        schema:
+          type: string
+      - description: 'Data filter field. Prefixes supported: > >= < <= @ ^ ! !@ !^'
+        in: query
+        name: tag
+        schema:
+          type: string
+      - description: 'Data filter field. Prefixes supported: > >= < <= @ ^ ! !@ !^'
+        in: query
+        name: topics
+        schema:
+          type: string
+      - description: 'Data filter field. Prefixes supported: > >= < <= @ ^ ! !@ !^'
+        in: query
+        name: txtype
+        schema:
+          type: string
+      - description: 'Data filter field. Prefixes supported: > >= < <= @ ^ ! !@ !^'
+        in: query
+        name: type
+        schema:
+          type: string
+      - description: Sort field. For multi-field sort use comma separated values (or
+          multiple query values) with '-' prefix for descending
+        in: query
+        name: sort
+        schema:
+          type: string
+      - description: Ascending sort order (overrides all fields in a multi-field sort)
+        in: query
+        name: ascending
+        schema:
+          type: string
+      - description: Descending sort order (overrides all fields in a multi-field
+          sort)
+        in: query
+        name: descending
+        schema:
+          type: string
+      - description: 'The number of records to skip (max: 1,000). Unsuitable for bulk
+          operations'
+        in: query
+        name: skip
+        schema:
+          type: string
+      - description: 'The maximum number of records to return (max: 1,000)'
+        in: query
+        name: limit
+        schema:
+          example: "25"
+          type: string
+      - description: Return a total count as well as items (adds extra database processing)
+        in: query
+        name: count
+        schema:
+          type: string
+      responses:
+        "200":
+          content:
+            application/json:
+              schema:
+                format: byte
+                type: string
+          description: Success
+        default:
+          description: ""
+      tags:
+      - Default Namespace
+  /data/{dataid}/value/publish:
+    post:
+      description: Publishes the JSON value from the specified data resource, to shared
+        storage
+      operationId: postDataValuePublish
+      parameters:
+      - description: The blob ID
+        in: path
+        name: dataid
+        required: true
+        schema:
+          type: string
+      - description: Server-side request timeout (milliseconds, or set a custom suffix
+          like 10s)
+        in: header
+        name: Request-Timeout
+        schema:
+          default: 2m0s
+          type: string
+      requestBody:
+        content:
+          application/json:
+            schema:
+              additionalProperties: {}
+              type: object
+      responses:
+        "200":
+          content:
+            application/json:
+              schema:
+                properties:
+                  blob:
+                    description: An optional hash reference to a binary blob attachment
+                    properties:
+                      hash:
+                        description: The hash of the binary blob data
+                        format: byte
+                        type: string
+                      name:
+                        description: The name field from the metadata attached to
+                          the blob, commonly used as a path/filename, and indexed
+                          for search
+                        type: string
+                      public:
+                        description: If the blob data has been published to shared
+                          storage, this field is the id of the data in the shared
+                          storage plugin (IPFS hash etc.)
+                        type: string
+                      size:
+                        description: The size of the binary data
+                        format: int64
+                        type: integer
+                    type: object
+                  created:
+                    description: The creation time of the data resource
+                    format: date-time
+                    type: string
+                  datatype:
+                    description: The optional datatype to use of validation of this
+                      data
+                    properties:
+                      name:
+                        description: The name of the datatype
+                        type: string
+                      version:
+                        description: The version of the datatype. Semantic versioning
+                          is encouraged, such as v1.0.1
+                        type: string
+                    type: object
+                  hash:
+                    description: The hash of the data resource. Derived from the value
+                      and the hash of any binary blob attachment
+                    format: byte
+                    type: string
+                  id:
+                    description: The UUID of the data resource
+                    format: uuid
+                    type: string
+                  namespace:
+                    description: The namespace of the data resource
+                    type: string
+                  public:
+                    description: If the JSON value has been published to shared storage,
+                      this field is the id of the data in the shared storage plugin
+                      (IPFS hash etc.)
+                    type: string
+                  validator:
+                    description: The data validator type
+                    type: string
+                  value:
+                    description: The value for the data, stored in the FireFly core
+                      database. Can be any JSON type - object, array, string, number
+                      or boolean. Can be combined with a binary blob attachment
+                type: object
+          description: Success
+        default:
+          description: ""
+      tags:
+      - Default Namespace
   /datatypes:
     get:
       description: Gets a list of datatypes that have been published
@@ -7014,6 +7382,7 @@ paths:
                           - token_transfer
                           - contract_invoke
                           - token_approval
+                          - data_publish
                           type: string
                         type:
                           description: The type of the message
@@ -7116,9 +7485,9 @@ paths:
                                 indexed for search
                               type: string
                             public:
-                              description: If this data has been published to shared
-                                storage, this field is the id of the data in the shared
-                                storage plugin (IPFS hash etc.)
+                              description: If the blob data has been published to
+                                shared storage, this field is the id of the data in
+                                the shared storage plugin (IPFS hash etc.)
                               type: string
                             size:
                               description: The size of the binary data
@@ -7256,6 +7625,7 @@ paths:
                         - token_transfer
                         - contract_invoke
                         - token_approval
+                        - data_publish
                         type: string
                       type:
                         description: The type of the message
@@ -7333,9 +7703,9 @@ paths:
                             for search
                           type: string
                         public:
-                          description: If this data has been published to shared storage,
-                            this field is the id of the data in the shared storage
-                            plugin (IPFS hash etc.)
+                          description: If the blob data has been published to shared
+                            storage, this field is the id of the data in the shared
+                            storage plugin (IPFS hash etc.)
                           type: string
                         size:
                           description: The size of the binary data
@@ -7370,6 +7740,11 @@ paths:
                     namespace:
                       description: The namespace of the data resource
                       type: string
+                    public:
+                      description: If the JSON value has been published to shared
+                        storage, this field is the id of the data in the shared storage
+                        plugin (IPFS hash etc.)
+                      type: string
                     validator:
                       description: The data validator type
                       type: string
@@ -7622,6 +7997,7 @@ paths:
                     - token_transfer
                     - contract_invoke
                     - token_approval
+                    - data_publish
                     type: string
                 type: object
           description: Success
@@ -7727,6 +8103,7 @@ paths:
                       - token_transfer
                       - contract_invoke
                       - token_approval
+                      - data_publish
                       type: string
                     type:
                       description: The type of the message
@@ -7833,6 +8210,7 @@ paths:
                         - token_transfer
                         - contract_invoke
                         - token_approval
+                        - data_publish
                         type: string
                       type:
                         description: The type of the message
@@ -7960,6 +8338,7 @@ paths:
                         - token_transfer
                         - contract_invoke
                         - token_approval
+                        - data_publish
                         type: string
                       type:
                         description: The type of the message
@@ -8135,6 +8514,7 @@ paths:
                       - token_transfer
                       - contract_invoke
                       - token_approval
+                      - data_publish
                       type: string
                     type:
                       description: The type of the message
@@ -8247,6 +8627,7 @@ paths:
                         - token_transfer
                         - contract_invoke
                         - token_approval
+                        - data_publish
                         type: string
                       type:
                         description: The type of the message
@@ -8380,6 +8761,7 @@ paths:
                         - token_transfer
                         - contract_invoke
                         - token_approval
+                        - data_publish
                         type: string
                       type:
                         description: The type of the message
@@ -8551,6 +8933,7 @@ paths:
                       - token_transfer
                       - contract_invoke
                       - token_approval
+                      - data_publish
                       type: string
                     type:
                       description: The type of the message
@@ -8603,9 +8986,9 @@ paths:
                                 indexed for search
                               type: string
                             public:
-                              description: If this data has been published to shared
-                                storage, this field is the id of the data in the shared
-                                storage plugin (IPFS hash etc.)
+                              description: If the blob data has been published to
+                                shared storage, this field is the id of the data in
+                                the shared storage plugin (IPFS hash etc.)
                               type: string
                             size:
                               description: The size of the binary data
@@ -8743,6 +9126,7 @@ paths:
                         - token_transfer
                         - contract_invoke
                         - token_approval
+                        - data_publish
                         type: string
                       type:
                         description: The type of the message
@@ -9640,6 +10024,7 @@ paths:
                     - blockchain_invoke
                     - sharedstorage_upload_batch
                     - sharedstorage_upload_blob
+                    - sharedstorage_upload_value
                     - sharedstorage_download_batch
                     - sharedstorage_download_blob
                     - dataexchange_send_batch
@@ -9712,6 +10097,7 @@ paths:
                     - blockchain_invoke
                     - sharedstorage_upload_batch
                     - sharedstorage_upload_blob
+                    - sharedstorage_upload_value
                     - sharedstorage_download_batch
                     - sharedstorage_download_blob
                     - dataexchange_send_batch
@@ -12495,6 +12881,7 @@ paths:
                     - blockchain_invoke
                     - sharedstorage_upload_batch
                     - sharedstorage_upload_blob
+                    - sharedstorage_upload_value
                     - sharedstorage_download_batch
                     - sharedstorage_download_blob
                     - dataexchange_send_batch
@@ -12567,6 +12954,7 @@ paths:
                     - blockchain_invoke
                     - sharedstorage_upload_batch
                     - sharedstorage_upload_blob
+                    - sharedstorage_upload_value
                     - sharedstorage_download_batch
                     - sharedstorage_download_blob
                     - dataexchange_send_batch
@@ -13381,6 +13769,11 @@ paths:
         name: id
         schema:
           type: string
+      - description: 'Data filter field. Prefixes supported: > >= < <= @ ^ ! !@ !^'
+        in: query
+        name: public
+        schema:
+          type: string
       - description: 'Data filter field. Prefixes supported: > >= < <= @ ^ ! !@ !^'
         in: query
         name: validator
@@ -13445,9 +13838,9 @@ paths:
                             for search
                           type: string
                         public:
-                          description: If this data has been published to shared storage,
-                            this field is the id of the data in the shared storage
-                            plugin (IPFS hash etc.)
+                          description: If the blob data has been published to shared
+                            storage, this field is the id of the data in the shared
+                            storage plugin (IPFS hash etc.)
                           type: string
                         size:
                           description: The size of the binary data
@@ -13482,6 +13875,11 @@ paths:
                     namespace:
                       description: The namespace of the data resource
                       type: string
+                    public:
+                      description: If the JSON value has been published to shared
+                        storage, this field is the id of the data in the shared storage
+                        plugin (IPFS hash etc.)
+                      type: string
                     validator:
                       description: The data validator type
                       type: string
@@ -13583,9 +13981,9 @@ paths:
                           for search
                         type: string
                       public:
-                        description: If this data has been published to shared storage,
-                          this field is the id of the data in the shared storage plugin
-                          (IPFS hash etc.)
+                        description: If the blob data has been published to shared
+                          storage, this field is the id of the data in the shared
+                          storage plugin (IPFS hash etc.)
                         type: string
                       size:
                         description: The size of the binary data
@@ -13620,6 +14018,11 @@ paths:
                   namespace:
                     description: The namespace of the data resource
                     type: string
+                  public:
+                    description: If the JSON value has been published to shared storage,
+                      this field is the id of the data in the shared storage plugin
+                      (IPFS hash etc.)
+                    type: string
                   validator:
                     description: The data validator type
                     type: string
@@ -13677,9 +14080,9 @@ paths:
                           for search
                         type: string
                       public:
-                        description: If this data has been published to shared storage,
-                          this field is the id of the data in the shared storage plugin
-                          (IPFS hash etc.)
+                        description: If the blob data has been published to shared
+                          storage, this field is the id of the data in the shared
+                          storage plugin (IPFS hash etc.)
                         type: string
                       size:
                         description: The size of the binary data
@@ -13714,6 +14117,11 @@ paths:
                   namespace:
                     description: The namespace of the data resource
                     type: string
+                  public:
+                    description: If the JSON value has been published to shared storage,
+                      this field is the id of the data in the shared storage plugin
+                      (IPFS hash etc.)
+                    type: string
                   validator:
                     description: The data validator type
                     type: string
@@ -13878,6 +14286,112 @@ paths:
           description: ""
       tags:
       - Non-Default Namespace
+  /namespaces/{ns}/data/{dataid}/blob/publish:
+    post:
+      description: Publishes the binary blob attachment stored in your local data
+        exchange, to shared storage
+      operationId: postDataBlobPublishNamespace
+      parameters:
+      - description: The blob ID
+        in: path
+        name: dataid
+        required: true
+        schema:
+          type: string
+      - description: The namespace which scopes this request
+        in: path
+        name: ns
+        required: true
+        schema:
+          example: default
+          type: string
+      - description: Server-side request timeout (milliseconds, or set a custom suffix
+          like 10s)
+        in: header
+        name: Request-Timeout
+        schema:
+          default: 2m0s
+          type: string
+      requestBody:
+        content:
+          application/json:
+            schema:
+              additionalProperties: {}
+              type: object
+      responses:
+        "200":
+          content:
+            application/json:
+              schema:
+                properties:
+                  blob:
+                    description: An optional hash reference to a binary blob attachment
+                    properties:
+                      hash:
+                        description: The hash of the binary blob data
+                        format: byte
+                        type: string
+                      name:
+                        description: The name field from the metadata attached to
+                          the blob, commonly used as a path/filename, and indexed
+                          for search
+                        type: string
+                      public:
+                        description: If the blob data has been published to shared
+                          storage, this field is the id of the data in the shared
+                          storage plugin (IPFS hash etc.)
+                        type: string
+                      size:
+                        description: The size of the binary data
+                        format: int64
+                        type: integer
+                    type: object
+                  created:
+                    description: The creation time of the data resource
+                    format: date-time
+                    type: string
+                  datatype:
+                    description: The optional datatype to use of validation of this
+                      data
+                    properties:
+                      name:
+                        description: The name of the datatype
+                        type: string
+                      version:
+                        description: The version of the datatype. Semantic versioning
+                          is encouraged, such as v1.0.1
+                        type: string
+                    type: object
+                  hash:
+                    description: The hash of the data resource. Derived from the value
+                      and the hash of any binary blob attachment
+                    format: byte
+                    type: string
+                  id:
+                    description: The UUID of the data resource
+                    format: uuid
+                    type: string
+                  namespace:
+                    description: The namespace of the data resource
+                    type: string
+                  public:
+                    description: If the JSON value has been published to shared storage,
+                      this field is the id of the data in the shared storage plugin
+                      (IPFS hash etc.)
+                    type: string
+                  validator:
+                    description: The data validator type
+                    type: string
+                  value:
+                    description: The value for the data, stored in the FireFly core
+                      database. Can be any JSON type - object, array, string, number
+                      or boolean. Can be combined with a binary blob attachment
+                type: object
+          description: Success
+        default:
+          description: ""
+      tags:
+      - Non-Default Namespace
   /namespaces/{ns}/data/{dataid}/messages:
     get:
       description: Gets a list of the messages associated with a data item
@@ -14116,6 +14630,7 @@ paths:
                         - token_transfer
                         - contract_invoke
                         - token_approval
+                        - data_publish
                         type: string
                       type:
                         description: The type of the message
@@ -14155,6 +14670,264 @@ paths:
           description: ""
       tags:
       - Non-Default Namespace
+  /namespaces/{ns}/data/{dataid}/value:
+    get:
+      description: Downloads the JSON value of the data resource, without the associated
+        metadata
+      operationId: getDataValueNamespace
+      parameters:
+      - description: The blob ID
+        in: path
+        name: dataid
+        required: true
+        schema:
+          type: string
+      - description: The namespace which scopes this request
+        in: path
+        name: ns
+        required: true
+        schema:
+          example: default
+          type: string
+      - description: Server-side request timeout (milliseconds, or set a custom suffix
+          like 10s)
+        in: header
+        name: Request-Timeout
+        schema:
+          default: 2m0s
+          type: string
+      - description: 'Data filter field. Prefixes supported: > >= < <= @ ^ ! !@ !^'
+        in: query
+        name: author
+        schema:
+          type: string
+      - description: 'Data filter field. Prefixes supported: > >= < <= @ ^ ! !@ !^'
+        in: query
+        name: batch
+        schema:
+          type: string
+      - description: 'Data filter field. Prefixes supported: > >= < <= @ ^ ! !@ !^'
+        in: query
+        name: cid
+        schema:
+          type: string
+      - description: 'Data filter field. Prefixes supported: > >= < <= @ ^ ! !@ !^'
+        in: query
+        name: confirmed
+        schema:
+          type: string
+      - description: 'Data filter field. Prefixes supported: > >= < <= @ ^ ! !@ !^'
+        in: query
+        name: created
+        schema:
+          type: string
+      - description: 'Data filter field. Prefixes supported: > >= < <= @ ^ ! !@ !^'
+        in: query
+        name: group
+        schema:
+          type: string
+      - description: 'Data filter field. Prefixes supported: > >= < <= @ ^ ! !@ !^'
+        in: query
+        name: hash
+        schema:
+          type: string
+      - description: 'Data filter field. Prefixes supported: > >= < <= @ ^ ! !@ !^'
+        in: query
+        name: id
+        schema:
+          type: string
+      - description: 'Data filter field. Prefixes supported: > >= < <= @ ^ ! !@ !^'
+        in: query
+        name: key
+        schema:
+          type: string
+      - description: 'Data filter field. Prefixes supported: > >= < <= @ ^ ! !@ !^'
+        in: query
+        name: pins
+        schema:
+          type: string
+      - description: 'Data filter field. Prefixes supported: > >= < <= @ ^ ! !@ !^'
+        in: query
+        name: sequence
+        schema:
+          type: string
+      - description: 'Data filter field. Prefixes supported: > >= < <= @ ^ ! !@ !^'
+        in: query
+        name: state
+        schema:
+          type: string
+      - description: 'Data filter field. Prefixes supported: > >= < <= @ ^ ! !@ !^'
+        in: query
+        name: tag
+        schema:
+          type: string
+      - description: 'Data filter field. Prefixes supported: > >= < <= @ ^ ! !@ !^'
+        in: query
+        name: topics
+        schema:
+          type: string
+      - description: 'Data filter field. Prefixes supported: > >= < <= @ ^ ! !@ !^'
+        in: query
+        name: txtype
+        schema:
+          type: string
+      - description: 'Data filter field. Prefixes supported: > >= < <= @ ^ ! !@ !^'
+        in: query
+        name: type
+        schema:
+          type: string
+      - description: Sort field. For multi-field sort use comma separated values (or
+          multiple query values) with '-' prefix for descending
+        in: query
+        name: sort
+        schema:
+          type: string
+      - description: Ascending sort order (overrides all fields in a multi-field sort)
+        in: query
+        name: ascending
+        schema:
+          type: string
+      - description: Descending sort order (overrides all fields in a multi-field
+          sort)
+        in: query
+        name: descending
+        schema:
+          type: string
+      - description: 'The number of records to skip (max: 1,000). Unsuitable for bulk
+          operations'
+        in: query
+        name: skip
+        schema:
+          type: string
+      - description: 'The maximum number of records to return (max: 1,000)'
+        in: query
+        name: limit
+        schema:
+          example: "25"
+          type: string
+      - description: Return a total count as well as items (adds extra database processing)
+        in: query
+        name: count
+        schema:
+          type: string
+      responses:
+        "200":
+          content:
+            application/json:
+              schema:
+                format: byte
+                type: string
+          description: Success
+        default:
+          description: ""
+      tags:
+      - Non-Default Namespace
+  /namespaces/{ns}/data/{dataid}/value/publish:
+    post:
+      description: Publishes the JSON value from the specified data resource, to shared
+        storage
+      operationId: postDataValuePublishNamespace
+      parameters:
+      - description: The blob ID
+        in: path
+        name: dataid
+        required: true
+        schema:
+          type: string
+      - description: The namespace which scopes this request
+        in: path
+        name: ns
+        required: true
+        schema:
+          example: default
+          type: string
+      - description: Server-side request timeout (milliseconds, or set a custom suffix
+          like 10s)
+        in: header
+        name: Request-Timeout
+        schema:
+          default: 2m0s
+          type: string
+      requestBody:
+        content:
+          application/json:
+            schema:
+              additionalProperties: {}
+              type: object
+      responses:
+        "200":
+          content:
+            application/json:
+              schema:
+                properties:
+                  blob:
+                    description: An optional hash reference to a binary blob attachment
+                    properties:
+                      hash:
+                        description: The hash of the binary blob data
+                        format: byte
+                        type: string
+                      name:
+                        description: The name field from the metadata attached to
+                          the blob, commonly used as a path/filename, and indexed
+                          for search
+                        type: string
+                      public:
+                        description: If the blob data has been published to shared
+                          storage, this field is the id of the data in the shared
+                          storage plugin (IPFS hash etc.)
+                        type: string
+                      size:
+                        description: The size of the binary data
+                        format: int64
+                        type: integer
+                    type: object
+                  created:
+                    description: The creation time of the data resource
+                    format: date-time
+                    type: string
+                  datatype:
+                    description: The optional datatype to use of validation of this
+                      data
+                    properties:
+                      name:
+                        description: The name of the datatype
+                        type: string
+                      version:
+                        description: The version of the datatype. Semantic versioning
+                          is encouraged, such as v1.0.1
+                        type: string
+                    type: object
+                  hash:
+                    description: The hash of the data resource. Derived from the value
+                      and the hash of any binary blob attachment
+                    format: byte
+                    type: string
+                  id:
+                    description: The UUID of the data resource
+                    format: uuid
+                    type: string
+                  namespace:
+                    description: The namespace of the data resource
+                    type: string
+                  public:
+                    description: If the JSON value has been published to shared storage,
+                      this field is the id of the data in the shared storage plugin
+                      (IPFS hash etc.)
+                    type: string
+                  validator:
+                    description: The data validator type
+                    type: string
+                  value:
+                    description: The value for the data, stored in the FireFly core
+                      database. Can be any JSON type - object, array, string, number
+                      or boolean. Can be combined with a binary blob attachment
+                type: object
+          description: Success
+        default:
+          description: ""
+      tags:
+      - Non-Default Namespace
   /namespaces/{ns}/datatypes:
     get:
       description: Gets a list of datatypes that have been published
@@ -16378,6 +17151,7 @@ paths:
                           - token_transfer
                           - contract_invoke
                           - token_approval
+                          - data_publish
                           type: string
                         type:
                           description: The type of the message
@@ -16487,9 +17261,9 @@ paths:
                                 indexed for search
                               type: string
                             public:
-                              description: If this data has been published to shared
-                                storage, this field is the id of the data in the shared
-                                storage plugin (IPFS hash etc.)
+                              description: If the blob data has been published to
+                                shared storage, this field is the id of the data in
+                                the shared storage plugin (IPFS hash etc.)
                               type: string
                             size:
                               description: The size of the binary data
@@ -16627,6 +17401,7 @@ paths:
                         - token_transfer
                         - contract_invoke
                         - token_approval
+                        - data_publish
                         type: string
                       type:
                         description: The type of the message
@@ -16711,9 +17486,9 @@ paths:
                             for search
                           type: string
                         public:
-                          description: If this data has been published to shared storage,
-                            this field is the id of the data in the shared storage
-                            plugin (IPFS hash etc.)
+                          description: If the blob data has been published to shared
+                            storage, this field is the id of the data in the shared
+                            storage plugin (IPFS hash etc.)
                           type: string
                         size:
                           description: The size of the binary data
@@ -16748,6 +17523,11 @@ paths:
                     namespace:
                       description: The namespace of the data resource
                       type: string
+                    public:
+                      description: If the JSON value has been published to shared
+                        storage, this field is the id of the data in the shared storage
+                        plugin (IPFS hash etc.)
+                      type: string
                     validator:
                       description: The data validator type
                       type: string
@@ -17014,6 +17794,7 @@ paths:
                     - token_transfer
                     - contract_invoke
                     - token_approval
+                    - data_publish
                     type: string
                 type: object
           description: Success
@@ -17164,6 +17945,7 @@ paths:
                       - token_transfer
                       - contract_invoke
                       - token_approval
+                      - data_publish
                       type: string
                     type:
                       description: The type of the message
@@ -17276,6 +18058,7 @@ paths:
                         - token_transfer
                         - contract_invoke
                         - token_approval
+                        - data_publish
                         type: string
                       type:
                         description: The type of the message
@@ -17409,6 +18192,7 @@ paths:
                         - token_transfer
                         - contract_invoke
                         - token_approval
+                        - data_publish
                         type: string
                       type:
                         description: The type of the message
@@ -17591,6 +18375,7 @@ paths:
                       - token_transfer
                       - contract_invoke
                       - token_approval
+                      - data_publish
                       type: string
                     type:
                       description: The type of the message
@@ -17703,6 +18488,7 @@ paths:
                         - token_transfer
                         - contract_invoke
                         - token_approval
+                        - data_publish
                         type: string
                       type:
                         description: The type of the message
@@ -17836,6 +18622,7 @@ paths:
                         - token_transfer
                         - contract_invoke
                         - token_approval
+                        - data_publish
                         type: string
                       type:
                         description: The type of the message
@@ -18014,6 +18801,7 @@ paths:
                       - token_transfer
                       - contract_invoke
                       - token_approval
+                      - data_publish
                       type: string
                     type:
                       description: The type of the message
@@ -18066,9 +18854,9 @@ paths:
                                 indexed for search
                               type: string
                             public:
-                              description: If this data has been published to shared
-                                storage, this field is the id of the data in the shared
-                                storage plugin (IPFS hash etc.)
+                              description: If the blob data has been published to
+                                shared storage, this field is the id of the data in
+                                the shared storage plugin (IPFS hash etc.)
                               type: string
                             size:
                               description: The size of the binary data
@@ -18206,6 +18994,7 @@ paths:
                         - token_transfer
                         - contract_invoke
                         - token_approval
+                        - data_publish
                         type: string
                       type:
                         description: The type of the message
@@ -19058,7 +19847,9 @@ paths:
       requestBody:
         content:
           application/json:
-            schema: {}
+            schema:
+              additionalProperties: {}
+              type: object
       responses:
         "200":
           content:
@@ -19751,7 +20542,9 @@ paths:
       requestBody:
         content:
           application/json:
-            schema: {}
+            schema:
+              additionalProperties: {}
+              type: object
       responses:
         "200":
           content:
@@ -20072,6 +20865,7 @@ paths:
                       - blockchain_invoke
                       - sharedstorage_upload_batch
                       - sharedstorage_upload_blob
+                      - sharedstorage_upload_value
                       - sharedstorage_download_batch
                       - sharedstorage_download_blob
                       - dataexchange_send_batch
@@ -20175,6 +20969,7 @@ paths:
                     - blockchain_invoke
                     - sharedstorage_upload_batch
                     - sharedstorage_upload_blob
+                    - sharedstorage_upload_value
                     - sharedstorage_download_batch
                     - sharedstorage_download_blob
                     - dataexchange_send_batch
@@ -20222,7 +21017,9 @@ paths:
       requestBody:
         content:
           application/json:
-            schema: {}
+            schema:
+              additionalProperties: {}
+              type: object
       responses:
         "202":
           content:
@@ -20281,6 +21078,7 @@ paths:
                     - blockchain_invoke
                     - sharedstorage_upload_batch
                     - sharedstorage_upload_blob
+                    - sharedstorage_upload_value
                     - sharedstorage_download_batch
                     - sharedstorage_download_blob
                     - dataexchange_send_batch
@@ -23249,6 +24047,7 @@ paths:
                           - token_transfer
                           - contract_invoke
                           - token_approval
+                          - data_publish
                           type: string
                         type:
                           description: The type of the message
@@ -23687,6 +24486,7 @@ paths:
                           - token_transfer
                           - contract_invoke
                           - token_approval
+                          - data_publish
                           type: string
                         type:
                           description: The type of the message
@@ -24903,6 +25703,7 @@ paths:
                           - token_transfer
                           - contract_invoke
                           - token_approval
+                          - data_publish
                           type: string
                         type:
                           description: The type of the message
@@ -25371,6 +26172,7 @@ paths:
                       - token_transfer
                       - contract_invoke
                       - token_approval
+                      - data_publish
                       type: string
                   type: object
                 type: array
@@ -25500,6 +26302,7 @@ paths:
                     - token_transfer
                     - contract_invoke
                     - token_approval
+                    - data_publish
                     type: string
                 type: object
           description: Success
@@ -25692,6 +26495,7 @@ paths:
                       - blockchain_invoke
                       - sharedstorage_upload_batch
                       - sharedstorage_upload_blob
+                      - sharedstorage_upload_value
                       - sharedstorage_download_batch
                       - sharedstorage_download_blob
                       - dataexchange_send_batch
@@ -26747,7 +27551,9 @@ paths:
       requestBody:
         content:
           application/json:
-            schema: {}
+            schema:
+              additionalProperties: {}
+              type: object
       responses:
         "200":
           content:
@@ -27412,7 +28218,9 @@ paths:
       requestBody:
         content:
           application/json:
-            schema: {}
+            schema:
+              additionalProperties: {}
+              type: object
       responses:
         "200":
           content:
@@ -27726,6 +28534,7 @@ paths:
                       - blockchain_invoke
                       - sharedstorage_upload_batch
                       - sharedstorage_upload_blob
+                      - sharedstorage_upload_value
                       - sharedstorage_download_batch
                       - sharedstorage_download_blob
                       - dataexchange_send_batch
@@ -27822,6 +28631,7 @@ paths:
                     - blockchain_invoke
                     - sharedstorage_upload_batch
                     - sharedstorage_upload_blob
+                    - sharedstorage_upload_value
                     - sharedstorage_download_batch
                     - sharedstorage_download_blob
                     - dataexchange_send_batch
@@ -27862,7 +28672,9 @@ paths:
       requestBody:
         content:
           application/json:
-            schema: {}
+            schema:
+              additionalProperties: {}
+              type: object
       responses:
         "202":
           content:
@@ -27921,6 +28733,7 @@ paths:
                     - blockchain_invoke
                     - sharedstorage_upload_batch
                     - sharedstorage_upload_blob
+                    - sharedstorage_upload_value
                     - sharedstorage_download_batch
                     - sharedstorage_download_blob
                     - dataexchange_send_batch
@@ -30784,6 +31597,7 @@ paths:
                           - token_transfer
                           - contract_invoke
                           - token_approval
+                          - data_publish
                           type: string
                         type:
                           description: The type of the message
@@ -31200,6 +32014,7 @@ paths:
                           - token_transfer
                           - contract_invoke
                           - token_approval
+                          - data_publish
                           type: string
                         type:
                           description: The type of the message
@@ -32381,6 +33196,7 @@ paths:
                           - token_transfer
                           - contract_invoke
                           - token_approval
+                          - data_publish
                           type: string
                         type:
                           description: The type of the message
@@ -32835,6 +33651,7 @@ paths:
                       - token_transfer
                       - contract_invoke
                       - token_approval
+                      - data_publish
                       type: string
                   type: object
                 type: array
@@ -32957,6 +33774,7 @@ paths:
                     - token_transfer
                     - contract_invoke
                     - token_approval
+                    - data_publish
                     type: string
                 type: object
           description: Success
@@ -33135,6 +33953,7 @@ paths:
                       - blockchain_invoke
                       - sharedstorage_upload_batch
                       - sharedstorage_upload_blob
+                      - sharedstorage_upload_value
                       - sharedstorage_download_batch
                       - sharedstorage_download_blob
                       - dataexchange_send_batch
diff --git a/internal/apiserver/route_get_data_value.go b/internal/apiserver/route_get_data_value.go
new file mode 100644
index 000000000..639d3f885
--- /dev/null
+++ b/internal/apiserver/route_get_data_value.go
@@ -0,0 +1,49 @@
+// Copyright © 2022 Kaleido, Inc.
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package apiserver
+
+import (
+	"net/http"
+
+	"github.com/hyperledger/firefly-common/pkg/ffapi"
+	"github.com/hyperledger/firefly/internal/coremsgs"
+	"github.com/hyperledger/firefly/pkg/database"
+)
+
+var getDataValue = &ffapi.Route{
+	Name:   "getDataValue",
+	Path:   "data/{dataid}/value",
+	Method: http.MethodGet,
+	PathParams: []*ffapi.PathParam{
+		{Name: "dataid", Description: coremsgs.APIParamsBlobID},
+	},
+	QueryParams:     nil,
+	Description:     coremsgs.APIEndpointsGetDataValue,
+	JSONInputValue:  nil,
+	JSONOutputValue: func() interface{} { return []byte{} },
+	JSONOutputCodes: []int{http.StatusOK},
+	Extensions: &coreExtensions{
+		FilterFactory: database.MessageQueryFactory,
+		CoreJSONHandler: func(r *ffapi.APIRequest, cr *coreRequest) (output interface{}, err error) {
+			d, err := cr.or.GetDataByID(cr.ctx, r.PP["dataid"])
+			if err != nil {
+				return nil, err
+			}
+			return d.Value, err
+		},
+	},
+}
diff --git a/internal/apiserver/route_post_data_blob_publish.go b/internal/apiserver/route_post_data_blob_publish.go
new file mode 100644
index 000000000..0063afa55
--- /dev/null
+++ b/internal/apiserver/route_post_data_blob_publish.go
@@ -0,0 +1,48 @@
+// Copyright © 2022 Kaleido, Inc.
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package apiserver
+
+import (
+	"net/http"
+
+	"github.com/hyperledger/firefly-common/pkg/ffapi"
+	"github.com/hyperledger/firefly/internal/coremsgs"
+	"github.com/hyperledger/firefly/internal/orchestrator"
+	"github.com/hyperledger/firefly/pkg/core"
+)
+
+var postDataBlobPublish = &ffapi.Route{
+	Name:   "postDataBlobPublish",
+	Path:   "data/{dataid}/blob/publish",
+	Method: http.MethodPost,
+	PathParams: []*ffapi.PathParam{
+		{Name: "dataid", Description: coremsgs.APIParamsBlobID},
+	},
+	QueryParams:     nil,
+	Description:     coremsgs.APIEndpointsPostDataBlobPublish,
+	JSONInputValue:  func() interface{} { return &core.EmptyInput{} },
+	JSONOutputValue: func() interface{} { return &core.Data{} },
+	JSONOutputCodes: []int{http.StatusOK},
+	Extensions: &coreExtensions{
+		EnabledIf: func(or orchestrator.Orchestrator) bool {
+			return or.MultiParty() != nil
+		},
+		CoreJSONHandler: func(r *ffapi.APIRequest, cr *coreRequest) (output interface{}, err error) {
+			return cr.or.Broadcast().PublishDataBlob(cr.ctx, r.PP["dataid"])
+		},
+	},
+}
diff --git a/internal/apiserver/route_post_data_value_publish.go b/internal/apiserver/route_post_data_value_publish.go
new file mode 100644
index 000000000..6f430696b
--- /dev/null
+++ b/internal/apiserver/route_post_data_value_publish.go
@@ -0,0 +1,48 @@
+// Copyright © 2022 Kaleido, Inc.
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package apiserver
+
+import (
+	"net/http"
+
+	"github.com/hyperledger/firefly-common/pkg/ffapi"
+	"github.com/hyperledger/firefly/internal/coremsgs"
+	"github.com/hyperledger/firefly/internal/orchestrator"
+	"github.com/hyperledger/firefly/pkg/core"
+)
+
+var postDataValuePublish = &ffapi.Route{
+	Name:   "postDataValuePublish",
+	Path:   "data/{dataid}/value/publish",
+	Method: http.MethodPost,
+	PathParams: []*ffapi.PathParam{
+		{Name: "dataid", Description: coremsgs.APIParamsBlobID},
+	},
+	QueryParams:     nil,
+	Description:     coremsgs.APIEndpointsPostDataValuePublish,
+	JSONInputValue:  func() interface{} { return &core.EmptyInput{} },
+	JSONOutputValue: func() interface{} { return &core.Data{} },
+	JSONOutputCodes: []int{http.StatusOK},
+	Extensions: &coreExtensions{
+		EnabledIf: func(or orchestrator.Orchestrator) bool {
+			return or.MultiParty() != nil
+		},
+		CoreJSONHandler: func(r *ffapi.APIRequest, cr *coreRequest) (output interface{}, err error) {
+			return cr.or.Broadcast().PublishDataValue(cr.ctx, r.PP["dataid"])
+		},
+	},
+}
diff --git a/internal/apiserver/routes.go b/internal/apiserver/routes.go
index 9550a71ec..5dc3d8609 100644
--- a/internal/apiserver/routes.go
+++ b/internal/apiserver/routes.go
@@ -73,6 +73,7 @@ var routes = append(
 		getContractListeners,
 		getData,
 		getDataBlob,
+		getDataValue,
 		getDataByID,
 		getDataMsgs,
 		getDatatypeByName,
@@ -129,6 +130,8 @@ var routes = append(
 		postContractInvoke,
 		postContractQuery,
 		postData,
+		postDataBlobPublish,
+		postDataValuePublish,
 		postNetworkAction,
 		postNewContractAPI,
 		postNewContractInterface,
diff --git a/internal/broadcast/manager.go b/internal/broadcast/manager.go
index e6add739d..2738193ad 100644
--- a/internal/broadcast/manager.go
+++ b/internal/broadcast/manager.go
@@ -32,6 +32,7 @@ import (
 	"github.com/hyperledger/firefly/internal/multiparty"
 	"github.com/hyperledger/firefly/internal/operations"
 	"github.com/hyperledger/firefly/internal/syncasync"
+	"github.com/hyperledger/firefly/internal/txcommon"
 	"github.com/hyperledger/firefly/pkg/blockchain"
 	"github.com/hyperledger/firefly/pkg/core"
 	"github.com/hyperledger/firefly/pkg/database"
@@ -46,6 +47,8 @@ type Manager interface {
 
 	NewBroadcast(in *core.MessageInOut) syncasync.Sender
 	BroadcastMessage(ctx context.Context, in *core.MessageInOut, waitConfirm bool) (out *core.Message, err error)
+	PublishDataValue(ctx context.Context, id string) (*core.Data, error)
+	PublishDataBlob(ctx context.Context, id string) (*core.Data, error)
 	Start() error
 	WaitStop()
 
@@ -68,10 +71,11 @@ type broadcastManager struct {
 	maxBatchPayloadLength int64
 	metrics               metrics.Manager
 	operations            operations.Manager
+	txHelper              txcommon.Helper
 }
 
-func NewBroadcastManager(ctx context.Context, ns *core.Namespace, di database.Plugin, bi blockchain.Plugin, dx dataexchange.Plugin, si sharedstorage.Plugin, im identity.Manager, dm data.Manager, ba batch.Manager, sa syncasync.Bridge, mult multiparty.Manager, mm metrics.Manager, om operations.Manager) (Manager, error) {
-	if di == nil || im == nil || dm == nil || bi == nil || dx == nil || si == nil || ba == nil || mm == nil || om == nil || mult == nil {
+func NewBroadcastManager(ctx context.Context, ns *core.Namespace, di database.Plugin, bi blockchain.Plugin, dx dataexchange.Plugin, si sharedstorage.Plugin, im identity.Manager, dm data.Manager, ba batch.Manager, sa syncasync.Bridge, mult multiparty.Manager, mm metrics.Manager, om operations.Manager, txHelper txcommon.Helper) (Manager, error) {
+	if di == nil || im == nil || dm == nil || bi == nil || dx == nil || si == nil || ba == nil || mm == nil || om == nil || mult == nil || txHelper == nil {
 		return nil, i18n.NewError(ctx, coremsgs.MsgInitializationNilDepError, "BroadcastManager")
 	}
 	bm := &broadcastManager{
@@ -88,6 +92,7 @@ func NewBroadcastManager(ctx context.Context, ns *core.Namespace, di database.Pl
 		maxBatchPayloadLength: config.GetByteSize(coreconfig.BroadcastBatchPayloadLimit),
 		metrics:               mm,
 		operations:            om,
+		txHelper:              txHelper,
 	}
 
 	bo := batch.DispatcherOptions{
@@ -109,6 +114,7 @@ func NewBroadcastManager(ctx context.Context, ns *core.Namespace, di database.Pl
 	om.RegisterHandler(ctx, bm, []core.OpType{
 		core.OpTypeSharedStorageUploadBatch,
 		core.OpTypeSharedStorageUploadBlob,
+		core.OpTypeSharedStorageUploadValue,
 	})
 
 	return bm, nil
@@ -138,7 +144,7 @@ func (bm *broadcastManager) dispatchBatch(ctx context.Context, state *batch.Disp
 	batch := state.Persisted.GenInflight(state.Messages, state.Data)
 
 	// We are in an (indefinite) retry cycle from the batch processor to dispatch this batch, that is only
-	// termianted with shutdown. So we leave the operation pending on failure, as it is still being retried.
+	// terminated with shutdown. So we leave the operation pending on failure, as it is still being retried.
 	// The user will still have the failure details recorded.
 	outputs, err := bm.operations.RunOperation(ctx, opUploadBatch(op, batch), operations.RemainPendingOnFailure)
 	if err != nil {
@@ -153,26 +159,7 @@ func (bm *broadcastManager) uploadBlobs(ctx context.Context, tx *fftypes.UUID, d
 	for _, d := range data {
 		// We only need to send a blob if there is one, and it's not been uploaded to the shared storage
 		if d.Blob != nil && d.Blob.Hash != nil && d.Blob.Public == "" {
-
-			op := core.NewOperation(
-				bm.sharedstorage,
-				bm.namespace.Name,
-				tx,
-				core.OpTypeSharedStorageUploadBlob)
-			addUploadBlobInputs(op, d.ID)
-			if err := bm.operations.AddOrReuseOperation(ctx, op); err != nil {
-				return err
-			}
-
-			blob, err := bm.database.GetBlobMatchingHash(ctx, d.Blob.Hash)
-			if err != nil {
-				return err
-			} else if blob == nil {
-				return i18n.NewError(ctx, coremsgs.MsgBlobNotFound, d.Blob.Hash)
-			}
-
-			_, err = bm.operations.RunOperation(ctx, opUploadBlob(op, d, blob))
-			if err != nil {
+			if err := bm.uploadDataBlob(ctx, tx, d); err != nil {
 				return err
 			}
 		}
@@ -181,6 +168,97 @@ func (bm *broadcastManager) uploadBlobs(ctx context.Context, tx *fftypes.UUID, d
 	return nil
 }
 
+func (bm *broadcastManager) resolveData(ctx context.Context, id string) (*core.Data, error) {
+	u, err := fftypes.ParseUUID(ctx, id)
+	if err != nil {
+		return nil, err
+	}
+
+	d, err := bm.database.GetDataByID(ctx, bm.namespace.Name, u, true)
+	if err != nil {
+		return nil, err
+	}
+	if d == nil {
+		return nil, i18n.NewError(ctx, coremsgs.Msg404NotFound)
+	}
+
+	return d, nil
+}
+
+func (bm *broadcastManager) uploadDataBlob(ctx context.Context, tx *fftypes.UUID, d *core.Data) error {
+	if d.Blob == nil || d.Blob.Hash == nil {
+		return i18n.NewError(ctx, coremsgs.MsgDataDoesNotHaveBlob)
+	}
+
+	op := core.NewOperation(
+		bm.sharedstorage,
+		bm.namespace.Name,
+		tx,
+		core.OpTypeSharedStorageUploadBlob)
+	addUploadBlobInputs(op, d.ID)
+	if err := bm.operations.AddOrReuseOperation(ctx, op); err != nil {
+		return err
+	}
+
+	blob, err := bm.database.GetBlobMatchingHash(ctx, d.Blob.Hash)
+	if err != nil {
+		return err
+	} else if blob == nil {
+		return i18n.NewError(ctx, coremsgs.MsgBlobNotFound, d.Blob.Hash)
+	}
+
+	_, err = bm.operations.RunOperation(ctx, opUploadBlob(op, d, blob))
+	return err
+}
+
+func (bm *broadcastManager) PublishDataValue(ctx context.Context, id string) (*core.Data, error) {
+
+	d, err := bm.resolveData(ctx, id)
+	if err != nil {
+		return nil, err
+	}
+
+	txid, err := bm.txHelper.SubmitNewTransaction(ctx, core.TransactionTypeDataPublish)
+	if err != nil {
+		return nil, err
+	}
+
+	op := core.NewOperation(
+		bm.sharedstorage,
+		bm.namespace.Name,
+		txid,
+		core.OpTypeSharedStorageUploadValue)
+	addUploadValueInputs(op, d.ID)
+	if err := bm.operations.AddOrReuseOperation(ctx, op); err != nil {
+		return nil, err
+	}
+
+	if _, err := bm.operations.RunOperation(ctx, opUploadValue(op, d)); err != nil {
+		return nil, err
+	}
+
+	return d, nil
+}
+
+func (bm *broadcastManager) PublishDataBlob(ctx context.Context, id string) (*core.Data, error) {
+
+	d, err := bm.resolveData(ctx, id)
+	if err != nil {
+		return nil, err
+	}
+
+	txid, err := bm.txHelper.SubmitNewTransaction(ctx, core.TransactionTypeDataPublish)
+	if err != nil {
+		return nil, err
+	}
+
+	if err = bm.uploadDataBlob(ctx, txid, d); err != nil {
+		return nil, err
+	}
+
+	return d, nil
+}
+
 func (bm *broadcastManager) Start() error {
 	return nil
 }
diff --git a/internal/broadcast/operations.go b/internal/broadcast/operations.go
index 16c10ea24..fce59adb1 100644
--- a/internal/broadcast/operations.go
+++ b/internal/broadcast/operations.go
@@ -35,7 +35,11 @@ type uploadBatchData struct {
 
 type uploadBlobData struct {
 	Data *core.Data `json:"data"`
-	Blob *core.Blob `json:"batch"`
+	Blob *core.Blob `json:"blob"`
+}
+
+type uploadValue struct {
+	Data *core.Data `json:"data"`
 }
 
 func addUploadBatchInputs(op *core.Operation, batchID *fftypes.UUID) {
@@ -56,6 +60,12 @@ func addUploadBlobInputs(op *core.Operation, dataID *fftypes.UUID) {
 	}
 }
 
+func addUploadValueInputs(op *core.Operation, dataID *fftypes.UUID) {
+	op.Input = fftypes.JSONObject{
+		"dataId": dataID.String(),
+	}
+}
+
 func getUploadBlobOutputs(payloadRef string) fftypes.JSONObject {
 	return fftypes.JSONObject{
 		"payloadRef": payloadRef,
@@ -70,6 +80,10 @@ func retrieveUploadBlobInputs(ctx context.Context, op *core.Operation) (*fftypes
 	return fftypes.ParseUUID(ctx, op.Input.GetString("dataId"))
 }
 
+func retrieveUploadValueInputs(ctx context.Context, op *core.Operation) (*fftypes.UUID, error) {
+	return fftypes.ParseUUID(ctx, op.Input.GetString("dataId"))
+}
+
 func (bm *broadcastManager) PrepareOperation(ctx context.Context, op *core.Operation) (*core.PreparedOperation, error) {
 	switch op.Type {
 	case core.OpTypeSharedStorageUploadBatch:
@@ -108,6 +122,18 @@ func (bm *broadcastManager) PrepareOperation(ctx context.Context, op *core.Opera
 		}
 		return opUploadBlob(op, d, blob), nil
 
+	case core.OpTypeSharedStorageUploadValue:
+		dataID, err := retrieveUploadValueInputs(ctx, op)
+		if err != nil {
+			return nil, err
+		}
+		d, err := bm.database.GetDataByID(ctx, bm.namespace.Name, dataID, false)
+		if err != nil {
+			return nil, err
+		} else if d == nil {
+			return nil, i18n.NewError(ctx, coremsgs.Msg404NotFound)
+		}
+		return opUploadValue(op, d), nil
 	default:
 		return nil, i18n.NewError(ctx, coremsgs.MsgOperationNotSupported, op.Type)
 	}
@@ -119,6 +145,8 @@ func (bm *broadcastManager) RunOperation(ctx context.Context, op *core.PreparedO
 		return bm.uploadBatch(ctx, data)
 	case uploadBlobData:
 		return bm.uploadBlob(ctx, data)
+	case uploadValue:
+		return bm.uploadValue(ctx, data)
 	default:
 		return nil, false, i18n.NewError(ctx, coremsgs.MsgOperationDataIncorrect, op.Data)
 	}
@@ -168,6 +196,25 @@ func (bm *broadcastManager) uploadBlob(ctx context.Context, data uploadBlobData)
 	return getUploadBlobOutputs(data.Data.Blob.Public), true, nil
 }
 
+// uploadValue streams the value JSON from a data record to public storage
+func (bm *broadcastManager) uploadValue(ctx context.Context, data uploadValue) (outputs fftypes.JSONObject, complete bool, err error) {
+
+	// Upload to shared storage
+	data.Data.Public, err = bm.sharedstorage.UploadData(ctx, bytes.NewReader(data.Data.Value.Bytes()))
+	if err != nil {
+		return nil, false, err
+	}
+
+	// Update the public reference for the data in the DB
+	err = bm.database.UpdateData(ctx, bm.namespace.Name, data.Data.ID, database.DataQueryFactory.NewUpdate(ctx).Set("public", data.Data.Public))
+	if err != nil {
+		return nil, false, err
+	}
+
+	log.L(ctx).Infof("Published value for data '%s' to shared storage: '%s'", data.Data.ID, data.Data.Public)
+	return getUploadBlobOutputs(data.Data.Public), true, nil
+}
+
 func (bm *broadcastManager) OnOperationUpdate(ctx context.Context, op *core.Operation, update *core.OperationUpdate) error {
 	return nil
 }
@@ -191,3 +238,13 @@ func opUploadBlob(op *core.Operation, data *core.Data, blob *core.Blob) *core.Pr
 		Data:      uploadBlobData{Data: data, Blob: blob},
 	}
 }
+
+func opUploadValue(op *core.Operation, data *core.Data) *core.PreparedOperation {
+	return &core.PreparedOperation{
+		ID:        op.ID,
+		Namespace: op.Namespace,
+		Plugin:    op.Plugin,
+		Type:      op.Type,
+		Data:      uploadValue{Data: data},
+	}
+}
diff --git a/internal/coremsgs/en_api_translations.go b/internal/coremsgs/en_api_translations.go
index 5548926a9..e91d82638 100644
--- a/internal/coremsgs/en_api_translations.go
+++ b/internal/coremsgs/en_api_translations.go
@@ -89,6 +89,7 @@ var (
 	APIEndpointsGetContractListenerByNameOrID   = ffm("api.endpoints.getContractListenerByNameOrID", "Gets a contract listener by its name or ID")
 	APIEndpointsGetContractListeners            = ffm("api.endpoints.getContractListeners", "Gets a list of contract listeners")
 	APIEndpointsGetDataBlob                     = ffm("api.endpoints.getDataBlob", "Downloads the original file that was previously uploaded or received")
+	APIEndpointsGetDataValue                    = ffm("api.endpoints.getDataValue", "Downloads the JSON value of the data resource, without the associated metadata")
 	APIEndpointsGetDataByID                     = ffm("api.endpoints.getDataByID", "Gets a data item by its ID, including metadata about this item")
 	APIEndpointsGetDataMsgs                     = ffm("api.endpoints.getDataMsgs", "Gets a list of the messages associated with a data item")
 	APIEndpointsGetData                         = ffm("api.endpoints.getData", "Gets a list of data items")
@@ -150,6 +151,8 @@ var (
 	APIEndpointsPostContractInvoke              = ffm("api.endpoints.postContractInvoke", "Invokes a method on a smart contract. Performs a blockchain transaction.")
 	APIEndpointsPostContractQuery               = ffm("api.endpoints.postContractQuery", "Queries a method on a smart contract. Performs a read-only query.")
 	APIEndpointsPostData                        = ffm("api.endpoints.postData", "Creates a new data item in this FireFly node")
+	APIEndpointsPostDataValuePublish            = ffm("api.endpoints.postDataValuePublish", "Publishes the JSON value from the specified data resource, to shared storage")
+	APIEndpointsPostDataBlobPublish             = ffm("api.endpoints.postDataBlobPublish", "Publishes the binary blob attachment stored in your local data exchange, to shared storage")
 	APIEndpointsPostNewContractAPI              = ffm("api.endpoints.postNewContractAPI", "Creates and broadcasts a new custom smart contract API")
 	APIEndpointsPostNewContractInterface        = ffm("api.endpoints.postNewContractInterface", "Creates and broadcasts a new custom smart contract interface")
 	APIEndpointsPostNewContractListener         = ffm("api.endpoints.postNewContractListener", "Creates a new blockchain listener for events emitted by custom smart contracts")
diff --git a/internal/coremsgs/en_struct_descriptions.go b/internal/coremsgs/en_struct_descriptions.go
index 1e982c1da..903b43453 100644
--- a/internal/coremsgs/en_struct_descriptions.go
+++ b/internal/coremsgs/en_struct_descriptions.go
@@ -112,7 +112,7 @@ var (
 	BlobRefHash   = ffm("BlobRef.hash", "The hash of the binary blob data")
 	BlobRefSize   = ffm("BlobRef.size", "The size of the binary data")
 	BlobRefName   = ffm("BlobRef.name", "The name field from the metadata attached to the blob, commonly used as a path/filename, and indexed for search")
-	BlobRefPublic = ffm("BlobRef.public", "If this data has been published to shared storage, this field is the id of the data in the shared storage plugin (IPFS hash etc.)")
+	BlobRefPublic = ffm("BlobRef.public", "If the blob data has been published to shared storage, this field is the id of the data in the shared storage plugin (IPFS hash etc.)")
 
 	// Data field descriptions
 	DataID        = ffm("Data.id", "The UUID of the data resource")
@@ -123,6 +123,7 @@ var (
 	DataDatatype  = ffm("Data.datatype", "The optional datatype to use of validation of this data")
 	DataValue     = ffm("Data.value", "The value for the data, stored in the FireFly core database. Can be any JSON type - object, array, string, number or boolean. Can be combined with a binary blob attachment")
 	DataBlob      = ffm("Data.blob", "An optional hash reference to a binary blob attachment")
+	DataPublic    = ffm("Data.public", "If the JSON value has been published to shared storage, this field is the id of the data in the shared storage plugin (IPFS hash etc.)")
 
 	// DatatypeRef field descriptions
 	DatatypeRefName    = ffm("DatatypeRef.name", "The name of the datatype")
diff --git a/internal/database/sqlcommon/data_sql.go b/internal/database/sqlcommon/data_sql.go
index 4f23bb5be..58d10f910 100644
--- a/internal/database/sqlcommon/data_sql.go
+++ b/internal/database/sqlcommon/data_sql.go
@@ -42,6 +42,7 @@ var (
 		"blob_public",
 		"blob_name",
 		"blob_size",
+		"public",
 		"value_size",
 	}
 	dataColumnsWithValue = append(append([]string{}, dataColumnsNoValue...), "value")
@@ -78,6 +79,7 @@ func (s *SQLCommon) attemptDataUpdate(ctx context.Context, tx *txWrapper, data *
 			Set("blob_public", blob.Public).
 			Set("blob_name", blob.Name).
 			Set("blob_size", blob.Size).
+			Set("public", data.Public).
 			Set("value_size", data.ValueSize).
 			Set("value", data.Value).
 			Where(sq.Eq{
@@ -111,6 +113,7 @@ func (s *SQLCommon) setDataInsertValues(query sq.InsertBuilder, data *core.Data)
 		blob.Public,
 		blob.Name,
 		blob.Size,
+		data.Public,
 		data.ValueSize,
 		data.Value,
 	)
@@ -235,6 +238,7 @@ func (s *SQLCommon) dataResult(ctx context.Context, row *sql.Rows, withValue boo
 		&data.Blob.Public,
 		&data.Blob.Name,
 		&data.Blob.Size,
+		&data.Public,
 		&data.ValueSize,
 	}
 	if withValue {
diff --git a/internal/orchestrator/orchestrator.go b/internal/orchestrator/orchestrator.go
index 92f4533eb..f3712f501 100644
--- a/internal/orchestrator/orchestrator.go
+++ b/internal/orchestrator/orchestrator.go
@@ -439,7 +439,7 @@ func (or *orchestrator) initManagers(ctx context.Context) (err error) {
 		}
 
 		if or.broadcast == nil {
-			if or.broadcast, err = broadcast.NewBroadcastManager(ctx, or.namespace, or.database(), or.blockchain(), or.dataexchange(), or.sharedstorage(), or.identity, or.data, or.batch, or.syncasync, or.multiparty, or.metrics, or.operations); err != nil {
+			if or.broadcast, err = broadcast.NewBroadcastManager(ctx, or.namespace, or.database(), or.blockchain(), or.dataexchange(), or.sharedstorage(), or.identity, or.data, or.batch, or.syncasync, or.multiparty, or.metrics, or.operations, or.txHelper); err != nil {
 				return err
 			}
 		}
diff --git a/internal/orchestrator/txn_status.go b/internal/orchestrator/txn_status.go
index 8e2412f06..f7abcb1b2 100644
--- a/internal/orchestrator/txn_status.go
+++ b/internal/orchestrator/txn_status.go
@@ -187,7 +187,7 @@ func (or *orchestrator) GetTransactionStatus(ctx context.Context, id string) (*c
 			})
 		}
 
-	case core.TransactionTypeContractInvoke:
+	case core.TransactionTypeContractInvoke, core.TransactionTypeDataPublish:
 		// no blockchain events or other objects
 
 	default:
diff --git a/mocks/broadcastmocks/manager.go b/mocks/broadcastmocks/manager.go
index 062d2a2ae..97b21222e 100644
--- a/mocks/broadcastmocks/manager.go
+++ b/mocks/broadcastmocks/manager.go
@@ -94,6 +94,52 @@ func (_m *Manager) PrepareOperation(ctx context.Context, op *core.Operation) (*c
 	return r0, r1
 }
 
+// PublishDataBlob provides a mock function with given fields: ctx, id
+func (_m *Manager) PublishDataBlob(ctx context.Context, id string) (*core.Data, error) {
+	ret := _m.Called(ctx, id)
+
+	var r0 *core.Data
+	if rf, ok := ret.Get(0).(func(context.Context, string) *core.Data); ok {
+		r0 = rf(ctx, id)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*core.Data)
+		}
+	}
+
+	var r1 error
+	if rf, ok := ret.Get(1).(func(context.Context, string) error); ok {
+		r1 = rf(ctx, id)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// PublishDataValue provides a mock function with given fields: ctx, id
+func (_m *Manager) PublishDataValue(ctx context.Context, id string) (*core.Data, error) {
+	ret := _m.Called(ctx, id)
+
+	var r0 *core.Data
+	if rf, ok := ret.Get(0).(func(context.Context, string) *core.Data); ok {
+		r0 = rf(ctx, id)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*core.Data)
+		}
+	}
+
+	var r1 error
+	if rf, ok := ret.Get(1).(func(context.Context, string) error); ok {
+		r1 = rf(ctx, id)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
 // RunOperation provides a mock function with given fields: ctx, op
 func (_m *Manager) RunOperation(ctx context.Context, op *core.PreparedOperation) (fftypes.JSONObject, bool, error) {
 	ret := _m.Called(ctx, op)
diff --git a/pkg/core/data.go b/pkg/core/data.go
index 44726eb8b..d586f3ed1 100644
--- a/pkg/core/data.go
+++ b/pkg/core/data.go
@@ -48,9 +48,10 @@ type Data struct {
 	Created   *fftypes.FFTime  `ffstruct:"Data" json:"created,omitempty"`
 	Datatype  *DatatypeRef     `ffstruct:"Data" json:"datatype,omitempty"`
 	Value     *fftypes.JSONAny `ffstruct:"Data" json:"value"`
+	Public    string           `ffstruct:"Data" json:"public,omitempty"`
 	Blob      *BlobRef         `ffstruct:"Data" json:"blob,omitempty"`
 
-	ValueSize int64 `json:"-"` // Used internally for message size calcuation, without full payload retrieval
+	ValueSize int64 `json:"-"` // Used internally for message size calculation, without full payload retrieval
 }
 
 func (br *BlobRef) BatchBlobRef(batchType BatchType) *BlobRef {
@@ -60,7 +61,7 @@ func (br *BlobRef) BatchBlobRef(batchType BatchType) *BlobRef {
 	switch batchType {
 	case BatchTypePrivate:
 		// For private we omit the "public" ref in all cases, to avoid an potential for the batch pay to change due
-		// to the same data being allocated by the same data being sent in a broadcast batch (thus assigining a public ref).
+		// to the same data being allocated by the same data being sent in a broadcast batch (thus assigning a public ref).
 		return &BlobRef{
 			Hash: br.Hash,
 			Size: br.Size,
diff --git a/pkg/core/emptyinput.go b/pkg/core/emptyinput.go
index 8b8dc2e08..7efccf09c 100644
--- a/pkg/core/emptyinput.go
+++ b/pkg/core/emptyinput.go
@@ -17,4 +17,4 @@
 package core
 
 // EmptyInput represents an API with no input fields, but requiring a JSON content type on the request
-type EmptyInput struct{}
+type EmptyInput map[string]interface{}
diff --git a/pkg/core/operation.go b/pkg/core/operation.go
index dc82c403c..c6824b7d4 100644
--- a/pkg/core/operation.go
+++ b/pkg/core/operation.go
@@ -41,6 +41,8 @@ var (
 	OpTypeSharedStorageUploadBatch = fftypes.FFEnumValue("optype", "sharedstorage_upload_batch")
 	// OpTypeSharedStorageUploadBlob is a shared storage operation to upload blob data
 	OpTypeSharedStorageUploadBlob = fftypes.FFEnumValue("optype", "sharedstorage_upload_blob")
+	// OpTypeSharedStorageUploadValue is a shared storage operation to upload the value JSON from a data record directly
+	OpTypeSharedStorageUploadValue = fftypes.FFEnumValue("optype", "sharedstorage_upload_value")
 	// OpTypeSharedStorageDownloadBatch is a shared storage operation to download broadcast data
 	OpTypeSharedStorageDownloadBatch = fftypes.FFEnumValue("optype", "sharedstorage_download_batch")
 	// OpTypeSharedStorageDownloadBlob is a shared storage operation to download broadcast data
diff --git a/pkg/core/transaction.go b/pkg/core/transaction.go
index 771a837ce..7ab5b54a0 100644
--- a/pkg/core/transaction.go
+++ b/pkg/core/transaction.go
@@ -39,6 +39,8 @@ var (
 	TransactionTypeContractInvoke = fftypes.FFEnumValue("txtype", "contract_invoke")
 	// TransactionTypeTokenTransfer represents a token approval
 	TransactionTypeTokenApproval = fftypes.FFEnumValue("txtype", "token_approval")
+	// TransactionTypeDataPublish represents a publish to shared storage
+	TransactionTypeDataPublish = fftypes.FFEnumValue("txtype", "data_publish")
 )
 
 // TransactionRef refers to a transaction, in other types
diff --git a/pkg/database/plugin.go b/pkg/database/plugin.go
index ad2e35795..95d125ad4 100644
--- a/pkg/database/plugin.go
+++ b/pkg/database/plugin.go
@@ -726,6 +726,7 @@ var DataQueryFactory = &queryFields{
 	"blob.size":        &Int64Field{},
 	"created":          &TimeField{},
 	"value":            &JSONField{},
+	"public":           &StringField{},
 }
 
 // DatatypeQueryFactory filter fields for data definitions


Test patch:
diff --git a/internal/apiserver/route_get_data_value_test.go b/internal/apiserver/route_get_data_value_test.go
new file mode 100644
index 000000000..18df1d35f
--- /dev/null
+++ b/internal/apiserver/route_get_data_value_test.go
@@ -0,0 +1,64 @@
+// Copyright © 2021 Kaleido, Inc.
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package apiserver
+
+import (
+	"fmt"
+	"io/ioutil"
+	"net/http/httptest"
+	"testing"
+
+	"github.com/hyperledger/firefly-common/pkg/fftypes"
+	"github.com/hyperledger/firefly/pkg/core"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/mock"
+)
+
+func TestGetDataValue(t *testing.T) {
+	o, r := newTestAPIServer()
+	o.On("Authorize", mock.Anything, mock.Anything).Return(nil)
+	req := httptest.NewRequest("GET", "/api/v1/namespaces/mynamespace/data/abcd12345/value", nil)
+	req.Header.Set("Content-Type", "application/json; charset=utf-8")
+	res := httptest.NewRecorder()
+
+	o.On("GetDataByID", mock.Anything, "abcd12345").
+		Return(&core.Data{
+			Value: fftypes.JSONAnyPtr(`{"some":"data"}`),
+		}, nil)
+	r.ServeHTTP(res, req)
+
+	assert.Equal(t, 200, res.Result().StatusCode)
+
+	resData, err := ioutil.ReadAll(res.Body)
+	assert.NoError(t, err)
+	assert.JSONEq(t, `{"some":"data"}`, string(resData))
+}
+
+func TestGetDataValueFail(t *testing.T) {
+	o, r := newTestAPIServer()
+	o.On("Authorize", mock.Anything, mock.Anything).Return(nil)
+	req := httptest.NewRequest("GET", "/api/v1/namespaces/mynamespace/data/abcd12345/value", nil)
+	req.Header.Set("Content-Type", "application/json; charset=utf-8")
+	res := httptest.NewRecorder()
+
+	o.On("GetDataByID", mock.Anything, "abcd12345").
+		Return(nil, fmt.Errorf("pop"))
+	r.ServeHTTP(res, req)
+
+	assert.Equal(t, 500, res.Result().StatusCode)
+
+}
diff --git a/internal/apiserver/route_post_data_blob_publish_test.go b/internal/apiserver/route_post_data_blob_publish_test.go
new file mode 100644
index 000000000..9bcab782a
--- /dev/null
+++ b/internal/apiserver/route_post_data_blob_publish_test.go
@@ -0,0 +1,50 @@
+// Copyright © 2021 Kaleido, Inc.
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package apiserver
+
+import (
+	"bytes"
+	"encoding/json"
+	"net/http/httptest"
+	"testing"
+
+	"github.com/hyperledger/firefly/mocks/broadcastmocks"
+	"github.com/hyperledger/firefly/mocks/multipartymocks"
+	"github.com/hyperledger/firefly/pkg/core"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/mock"
+)
+
+func TestPostDataBlobPublish(t *testing.T) {
+	o, r := newTestAPIServer()
+	o.On("Authorize", mock.Anything, mock.Anything).Return(nil)
+	mbm := &broadcastmocks.Manager{}
+	o.On("MultiParty").Return(&multipartymocks.Manager{})
+	o.On("Broadcast").Return(mbm)
+	input := core.Data{}
+	var buf bytes.Buffer
+	json.NewEncoder(&buf).Encode(&input)
+	req := httptest.NewRequest("POST", "/api/v1/namespaces/ns1/data/id1/blob/publish", &buf)
+	req.Header.Set("Content-Type", "application/json; charset=utf-8")
+	res := httptest.NewRecorder()
+
+	mbm.On("PublishDataBlob", mock.Anything, "id1").
+		Return(&core.Data{}, nil)
+	r.ServeHTTP(res, req)
+
+	assert.Equal(t, 200, res.Result().StatusCode)
+}
diff --git a/internal/apiserver/route_post_data_value_publish_test.go b/internal/apiserver/route_post_data_value_publish_test.go
new file mode 100644
index 000000000..0aeb9f05b
--- /dev/null
+++ b/internal/apiserver/route_post_data_value_publish_test.go
@@ -0,0 +1,50 @@
+// Copyright © 2021 Kaleido, Inc.
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package apiserver
+
+import (
+	"bytes"
+	"encoding/json"
+	"net/http/httptest"
+	"testing"
+
+	"github.com/hyperledger/firefly/mocks/broadcastmocks"
+	"github.com/hyperledger/firefly/mocks/multipartymocks"
+	"github.com/hyperledger/firefly/pkg/core"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/mock"
+)
+
+func TestPostDataValuePublish(t *testing.T) {
+	o, r := newTestAPIServer()
+	o.On("Authorize", mock.Anything, mock.Anything).Return(nil)
+	mbm := &broadcastmocks.Manager{}
+	o.On("MultiParty").Return(&multipartymocks.Manager{})
+	o.On("Broadcast").Return(mbm)
+	input := core.Data{}
+	var buf bytes.Buffer
+	json.NewEncoder(&buf).Encode(&input)
+	req := httptest.NewRequest("POST", "/api/v1/namespaces/ns1/data/id1/value/publish", &buf)
+	req.Header.Set("Content-Type", "application/json; charset=utf-8")
+	res := httptest.NewRecorder()
+
+	mbm.On("PublishDataValue", mock.Anything, "id1").
+		Return(&core.Data{}, nil)
+	r.ServeHTTP(res, req)
+
+	assert.Equal(t, 200, res.Result().StatusCode)
+}
diff --git a/internal/broadcast/manager_test.go b/internal/broadcast/manager_test.go
index 8d9fb4820..04c0a1e0e 100644
--- a/internal/broadcast/manager_test.go
+++ b/internal/broadcast/manager_test.go
@@ -37,6 +37,7 @@ import (
 	"github.com/hyperledger/firefly/mocks/operationmocks"
 	"github.com/hyperledger/firefly/mocks/sharedstoragemocks"
 	"github.com/hyperledger/firefly/mocks/syncasyncmocks"
+	"github.com/hyperledger/firefly/mocks/txcommonmocks"
 	"github.com/hyperledger/firefly/pkg/core"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/mock"
@@ -55,6 +56,7 @@ func newTestBroadcastCommon(t *testing.T, metricsEnabled bool) (*broadcastManage
 	mmp := &multipartymocks.Manager{}
 	mmi := &metricsmocks.Manager{}
 	mom := &operationmocks.Manager{}
+	mtx := &txcommonmocks.Helper{}
 	mmi.On("IsMetricsEnabled").Return(metricsEnabled)
 	mbi.On("Name").Return("ut_blockchain").Maybe()
 	mpi.On("Name").Return("ut_sharedstorage").Maybe()
@@ -77,7 +79,7 @@ func newTestBroadcastCommon(t *testing.T, metricsEnabled bool) (*broadcastManage
 
 	ctx, cancel := context.WithCancel(context.Background())
 	ns := &core.Namespace{Name: "ns1", NetworkName: "ns1"}
-	b, err := NewBroadcastManager(ctx, ns, mdi, mbi, mdx, mpi, mim, mdm, mba, msa, mmp, mmi, mom)
+	b, err := NewBroadcastManager(ctx, ns, mdi, mbi, mdx, mpi, mim, mdm, mba, msa, mmp, mmi, mom, mtx)
 	assert.NoError(t, err)
 	return b.(*broadcastManager), cancel
 }
@@ -94,7 +96,7 @@ func newTestBroadcastWithMetrics(t *testing.T) (*broadcastManager, func()) {
 }
 
 func TestInitFail(t *testing.T) {
-	_, err := NewBroadcastManager(context.Background(), &core.Namespace{}, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil)
+	_, err := NewBroadcastManager(context.Background(), &core.Namespace{}, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil)
 	assert.Regexp(t, "FF10128", err)
 }
 
@@ -301,21 +303,29 @@ func TestDispatchBatchSubmitBroadcastFail(t *testing.T) {
 	mom.AssertExpectations(t)
 }
 
-func TestUploadBlobsPublishFail(t *testing.T) {
+func TestUploadBlobPublishFail(t *testing.T) {
 	bm, cancel := newTestBroadcast(t)
 	defer cancel()
 	mdx := bm.exchange.(*dataexchangemocks.Plugin)
 	mps := bm.sharedstorage.(*sharedstoragemocks.Plugin)
 	mdi := bm.database.(*databasemocks.Plugin)
 	mom := bm.operations.(*operationmocks.Manager)
+	mtx := bm.txHelper.(*txcommonmocks.Helper)
 
 	blob := &core.Blob{
 		Hash:       fftypes.NewRandB32(),
 		PayloadRef: "blob/1",
 	}
-	dataID := fftypes.NewUUID()
+	d := &core.Data{
+		ID: fftypes.NewUUID(),
+		Blob: &core.BlobRef{
+			Hash: blob.Hash,
+		},
+	}
 
 	ctx := context.Background()
+	mtx.On("SubmitNewTransaction", mock.Anything, core.TransactionTypeDataPublish).Return(fftypes.NewUUID(), nil)
+	mdi.On("GetDataByID", ctx, "ns1", d.ID, true).Return(d, nil)
 	mdi.On("GetBlobMatchingHash", ctx, blob.Hash).Return(blob, nil)
 	mom.On("AddOrReuseOperation", mock.Anything, mock.Anything).Return(nil)
 	mom.On("RunOperation", mock.Anything, mock.MatchedBy(func(op *core.PreparedOperation) bool {
@@ -323,14 +333,7 @@ func TestUploadBlobsPublishFail(t *testing.T) {
 		return op.Type == core.OpTypeSharedStorageUploadBlob && data.Blob == blob
 	})).Return(nil, fmt.Errorf("pop"))
 
-	err := bm.uploadBlobs(ctx, fftypes.NewUUID(), core.DataArray{
-		{
-			ID: dataID,
-			Blob: &core.BlobRef{
-				Hash: blob.Hash,
-			},
-		},
-	})
+	_, err := bm.PublishDataBlob(ctx, d.ID.String())
 	assert.EqualError(t, err, "pop")
 
 	mdi.AssertExpectations(t)
@@ -430,3 +433,243 @@ func TestUploadBlobsGetBlobInsertOpFail(t *testing.T) {
 	mom.AssertExpectations(t)
 
 }
+
+func TestUploadValueNotFound(t *testing.T) {
+	bm, cancel := newTestBroadcast(t)
+	defer cancel()
+
+	mdi := bm.database.(*databasemocks.Plugin)
+	mdi.On("GetDataByID", mock.Anything, "ns1", mock.Anything, true).Return(nil, nil)
+
+	_, err := bm.PublishDataValue(bm.ctx, fftypes.NewUUID().String())
+	assert.Regexp(t, "FF10109", err)
+
+	mdi.AssertExpectations(t)
+}
+
+func TestUploadBlobLookupError(t *testing.T) {
+	bm, cancel := newTestBroadcast(t)
+	defer cancel()
+
+	mdi := bm.database.(*databasemocks.Plugin)
+	mdi.On("GetDataByID", mock.Anything, "ns1", mock.Anything, true).Return(nil, fmt.Errorf("pop"))
+
+	_, err := bm.PublishDataBlob(bm.ctx, fftypes.NewUUID().String())
+	assert.Regexp(t, "pop", err)
+
+	mdi.AssertExpectations(t)
+}
+
+func TestUploadValueBadID(t *testing.T) {
+	bm, cancel := newTestBroadcast(t)
+	defer cancel()
+
+	_, err := bm.PublishDataValue(bm.ctx, "badness")
+	assert.Regexp(t, "FF00138", err)
+
+}
+
+func TestUploadValueFailPrepare(t *testing.T) {
+	bm, cancel := newTestBroadcast(t)
+	defer cancel()
+
+	d := &core.Data{
+		ID:    fftypes.NewUUID(),
+		Value: fftypes.JSONAnyPtr(`{"some": "value"}`),
+	}
+
+	mdi := bm.database.(*databasemocks.Plugin)
+	mdi.On("GetDataByID", mock.Anything, "ns1", d.ID, true).Return(d, nil)
+
+	mom := bm.operations.(*operationmocks.Manager)
+	mom.On("AddOrReuseOperation", mock.Anything, mock.Anything).Return(fmt.Errorf("pop"))
+
+	mtx := bm.txHelper.(*txcommonmocks.Helper)
+	mtx.On("SubmitNewTransaction", mock.Anything, core.TransactionTypeDataPublish).Return(fftypes.NewUUID(), nil)
+
+	_, err := bm.PublishDataValue(bm.ctx, d.ID.String())
+	assert.EqualError(t, err, "pop")
+
+	mom.AssertExpectations(t)
+	mdi.AssertExpectations(t)
+}
+
+func TestUploadValueFail(t *testing.T) {
+	bm, cancel := newTestBroadcast(t)
+	defer cancel()
+
+	d := &core.Data{
+		ID:    fftypes.NewUUID(),
+		Value: fftypes.JSONAnyPtr(`{"some": "value"}`),
+	}
+
+	mdi := bm.database.(*databasemocks.Plugin)
+	mdi.On("GetDataByID", mock.Anything, "ns1", d.ID, true).Return(d, nil)
+
+	mom := bm.operations.(*operationmocks.Manager)
+	mom.On("AddOrReuseOperation", mock.Anything, mock.Anything).Return(nil)
+	mom.On("RunOperation", mock.Anything, mock.MatchedBy(func(op *core.PreparedOperation) bool {
+		data := op.Data.(uploadValue)
+		return op.Type == core.OpTypeSharedStorageUploadValue && data.Data.ID.Equals(d.ID)
+	})).Return(nil, fmt.Errorf("pop"))
+
+	mtx := bm.txHelper.(*txcommonmocks.Helper)
+	mtx.On("SubmitNewTransaction", mock.Anything, core.TransactionTypeDataPublish).Return(fftypes.NewUUID(), nil)
+
+	_, err := bm.PublishDataValue(bm.ctx, d.ID.String())
+	assert.EqualError(t, err, "pop")
+
+	mom.AssertExpectations(t)
+	mdi.AssertExpectations(t)
+}
+
+func TestUploadValueOK(t *testing.T) {
+	bm, cancel := newTestBroadcast(t)
+	defer cancel()
+
+	d := &core.Data{
+		ID:    fftypes.NewUUID(),
+		Value: fftypes.JSONAnyPtr(`{"some": "value"}`),
+	}
+
+	mdi := bm.database.(*databasemocks.Plugin)
+	mdi.On("GetDataByID", mock.Anything, "ns1", d.ID, true).Return(d, nil)
+
+	mom := bm.operations.(*operationmocks.Manager)
+	mom.On("AddOrReuseOperation", mock.Anything, mock.Anything).Return(nil)
+	mom.On("RunOperation", mock.Anything, mock.MatchedBy(func(op *core.PreparedOperation) bool {
+		data := op.Data.(uploadValue)
+		return op.Type == core.OpTypeSharedStorageUploadValue && data.Data.ID.Equals(d.ID)
+	})).Return(nil, nil)
+
+	mtx := bm.txHelper.(*txcommonmocks.Helper)
+	mtx.On("SubmitNewTransaction", mock.Anything, core.TransactionTypeDataPublish).Return(fftypes.NewUUID(), nil)
+
+	d1, err := bm.PublishDataValue(bm.ctx, d.ID.String())
+	assert.NoError(t, err)
+	assert.Equal(t, d.ID, d1.ID)
+
+	mom.AssertExpectations(t)
+	mdi.AssertExpectations(t)
+}
+
+func TestUploadBlobFailNoBlob(t *testing.T) {
+	bm, cancel := newTestBroadcast(t)
+	defer cancel()
+
+	d := &core.Data{
+		ID:   fftypes.NewUUID(),
+		Blob: nil,
+	}
+
+	mdi := bm.database.(*databasemocks.Plugin)
+	mdi.On("GetDataByID", mock.Anything, "ns1", d.ID, true).Return(d, nil)
+
+	mtx := bm.txHelper.(*txcommonmocks.Helper)
+	mtx.On("SubmitNewTransaction", mock.Anything, core.TransactionTypeDataPublish).Return(fftypes.NewUUID(), nil)
+
+	_, err := bm.PublishDataBlob(bm.ctx, d.ID.String())
+	assert.Regexp(t, "FF10241", err)
+
+	mdi.AssertExpectations(t)
+}
+
+func TestUploadBlobOK(t *testing.T) {
+	bm, cancel := newTestBroadcast(t)
+	defer cancel()
+	mdx := bm.exchange.(*dataexchangemocks.Plugin)
+	mps := bm.sharedstorage.(*sharedstoragemocks.Plugin)
+	mdi := bm.database.(*databasemocks.Plugin)
+	mom := bm.operations.(*operationmocks.Manager)
+
+	blob := &core.Blob{
+		Hash:       fftypes.NewRandB32(),
+		PayloadRef: "blob/1",
+	}
+	d := &core.Data{
+		ID: fftypes.NewUUID(),
+		Blob: &core.BlobRef{
+			Hash: blob.Hash,
+		},
+	}
+
+	ctx := context.Background()
+	mdi.On("GetDataByID", ctx, "ns1", d.ID, true).Return(d, nil)
+	mdi.On("GetBlobMatchingHash", ctx, blob.Hash).Return(blob, nil)
+	mom.On("AddOrReuseOperation", mock.Anything, mock.Anything).Return(nil)
+	mom.On("RunOperation", mock.Anything, mock.MatchedBy(func(op *core.PreparedOperation) bool {
+		data := op.Data.(uploadBlobData)
+		return op.Type == core.OpTypeSharedStorageUploadBlob && data.Blob == blob
+	})).Return(nil, nil)
+
+	mtx := bm.txHelper.(*txcommonmocks.Helper)
+	mtx.On("SubmitNewTransaction", mock.Anything, core.TransactionTypeDataPublish).Return(fftypes.NewUUID(), nil)
+
+	d1, err := bm.PublishDataBlob(ctx, d.ID.String())
+	assert.NoError(t, err)
+	assert.Equal(t, d.ID, d1.ID)
+
+	mdi.AssertExpectations(t)
+	mdx.AssertExpectations(t)
+	mps.AssertExpectations(t)
+
+}
+
+func TestUploadBlobTXFail(t *testing.T) {
+	bm, cancel := newTestBroadcast(t)
+	defer cancel()
+	mdx := bm.exchange.(*dataexchangemocks.Plugin)
+	mps := bm.sharedstorage.(*sharedstoragemocks.Plugin)
+	mdi := bm.database.(*databasemocks.Plugin)
+
+	blob := &core.Blob{
+		Hash:       fftypes.NewRandB32(),
+		PayloadRef: "blob/1",
+	}
+	d := &core.Data{
+		ID: fftypes.NewUUID(),
+		Blob: &core.BlobRef{
+			Hash: blob.Hash,
+		},
+	}
+
+	ctx := context.Background()
+	mdi.On("GetDataByID", ctx, "ns1", d.ID, true).Return(d, nil)
+
+	mtx := bm.txHelper.(*txcommonmocks.Helper)
+	mtx.On("SubmitNewTransaction", mock.Anything, core.TransactionTypeDataPublish).Return(nil, fmt.Errorf("pop"))
+
+	_, err := bm.PublishDataBlob(ctx, d.ID.String())
+	assert.Regexp(t, "pop", err)
+
+	mdi.AssertExpectations(t)
+	mdx.AssertExpectations(t)
+	mps.AssertExpectations(t)
+
+}
+
+func TestUploadValueTXFail(t *testing.T) {
+	bm, cancel := newTestBroadcast(t)
+	defer cancel()
+	mdx := bm.exchange.(*dataexchangemocks.Plugin)
+	mps := bm.sharedstorage.(*sharedstoragemocks.Plugin)
+	mdi := bm.database.(*databasemocks.Plugin)
+
+	d := &core.Data{
+		ID: fftypes.NewUUID(),
+	}
+
+	ctx := context.Background()
+	mdi.On("GetDataByID", ctx, "ns1", d.ID, true).Return(d, nil)
+
+	mtx := bm.txHelper.(*txcommonmocks.Helper)
+	mtx.On("SubmitNewTransaction", mock.Anything, core.TransactionTypeDataPublish).Return(nil, fmt.Errorf("pop"))
+
+	_, err := bm.PublishDataValue(ctx, d.ID.String())
+	assert.Regexp(t, "pop", err)
+
+	mdi.AssertExpectations(t)
+	mdx.AssertExpectations(t)
+	mps.AssertExpectations(t)
+
+}
diff --git a/internal/broadcast/operations_test.go b/internal/broadcast/operations_test.go
index 046c8fb99..d3cba856c 100644
--- a/internal/broadcast/operations_test.go
+++ b/internal/broadcast/operations_test.go
@@ -284,6 +284,48 @@ func TestPrepareAndRunUploadBlob(t *testing.T) {
 
 }
 
+func TestPrepareAndRunValue(t *testing.T) {
+	bm, cancel := newTestBroadcast(t)
+	defer cancel()
+
+	op := &core.Operation{
+		Type: core.OpTypeSharedStorageUploadValue,
+	}
+	data := &core.Data{
+		ID:    fftypes.NewUUID(),
+		Value: fftypes.JSONAnyPtr(`{"some":"data"}`),
+	}
+	addUploadValueInputs(op, data.ID)
+
+	mps := bm.sharedstorage.(*sharedstoragemocks.Plugin)
+	mdi := bm.database.(*databasemocks.Plugin)
+
+	mdi.On("GetDataByID", mock.Anything, "ns1", data.ID, false).Return(data, nil)
+	mps.On("UploadData", context.Background(), mock.Anything).Return("123", nil)
+	mdi.On("UpdateData", context.Background(), "ns1", data.ID, mock.MatchedBy(func(update database.Update) bool {
+		info, _ := update.Finalize()
+		assert.Equal(t, 1, len(info.SetOperations))
+		assert.Equal(t, "public", info.SetOperations[0].Field)
+		val, _ := info.SetOperations[0].Value.Value()
+		assert.Equal(t, "123", val)
+		return true
+	})).Return(nil)
+
+	po, err := bm.PrepareOperation(context.Background(), op)
+	assert.NoError(t, err)
+	assert.Equal(t, data, po.Data.(uploadValue).Data)
+
+	outputs, complete, err := bm.RunOperation(context.Background(), opUploadValue(op, data))
+	assert.Equal(t, "123", outputs["payloadRef"])
+
+	assert.True(t, complete)
+	assert.NoError(t, err)
+
+	mps.AssertExpectations(t)
+	mdi.AssertExpectations(t)
+
+}
+
 func TestPrepareUploadBlobGetBlobMissing(t *testing.T) {
 	bm, cancel := newTestBroadcast(t)
 	defer cancel()
@@ -318,7 +360,7 @@ func TestPrepareUploadBlobGetBlobMissing(t *testing.T) {
 
 }
 
-func TestPrepareUploadBlobGetBlobFailg(t *testing.T) {
+func TestPrepareUploadBlobGetBlobFailing(t *testing.T) {
 	bm, cancel := newTestBroadcast(t)
 	defer cancel()
 
@@ -369,6 +411,27 @@ func TestPrepareUploadBlobGetDataMissing(t *testing.T) {
 
 }
 
+func TestPrepareUploadValueGetDataMissing(t *testing.T) {
+	bm, cancel := newTestBroadcast(t)
+	defer cancel()
+
+	op := &core.Operation{
+		Type: core.OpTypeSharedStorageUploadValue,
+	}
+	dataID := fftypes.NewUUID()
+	addUploadBlobInputs(op, dataID)
+
+	mdi := bm.database.(*databasemocks.Plugin)
+
+	mdi.On("GetDataByID", mock.Anything, "ns1", dataID, false).Return(nil, nil)
+
+	_, err := bm.PrepareOperation(context.Background(), op)
+	assert.Regexp(t, "FF10109", err)
+
+	mdi.AssertExpectations(t)
+
+}
+
 func TestPrepareUploadBlobGetDataFail(t *testing.T) {
 	bm, cancel := newTestBroadcast(t)
 	defer cancel()
@@ -390,6 +453,27 @@ func TestPrepareUploadBlobGetDataFail(t *testing.T) {
 
 }
 
+func TestPrepareUploadValueGetDataFail(t *testing.T) {
+	bm, cancel := newTestBroadcast(t)
+	defer cancel()
+
+	op := &core.Operation{
+		Type: core.OpTypeSharedStorageUploadValue,
+	}
+	dataID := fftypes.NewUUID()
+	addUploadBlobInputs(op, dataID)
+
+	mdi := bm.database.(*databasemocks.Plugin)
+
+	mdi.On("GetDataByID", mock.Anything, "ns1", dataID, false).Return(nil, fmt.Errorf("pop"))
+
+	_, err := bm.PrepareOperation(context.Background(), op)
+	assert.Regexp(t, "pop", err)
+
+	mdi.AssertExpectations(t)
+
+}
+
 func TestPrepareUploadBlobGetDataBadID(t *testing.T) {
 	bm, cancel := newTestBroadcast(t)
 	defer cancel()
@@ -403,6 +487,19 @@ func TestPrepareUploadBlobGetDataBadID(t *testing.T) {
 
 }
 
+func TestPrepareUploadValueGetDataBadID(t *testing.T) {
+	bm, cancel := newTestBroadcast(t)
+	defer cancel()
+
+	op := &core.Operation{
+		Type: core.OpTypeSharedStorageUploadValue,
+	}
+
+	_, err := bm.PrepareOperation(context.Background(), op)
+	assert.Regexp(t, "FF00138", err)
+
+}
+
 func TestRunOperationUploadBlobUpdateFail(t *testing.T) {
 	bm, cancel := newTestBroadcast(t)
 	defer cancel()
@@ -437,6 +534,31 @@ func TestRunOperationUploadBlobUpdateFail(t *testing.T) {
 	mdi.AssertExpectations(t)
 }
 
+func TestRunOperationUploadValueUpdateFail(t *testing.T) {
+	bm, cancel := newTestBroadcast(t)
+	defer cancel()
+
+	op := &core.Operation{}
+	data := &core.Data{
+		ID:    fftypes.NewUUID(),
+		Value: fftypes.JSONAnyPtr(`{"some":"data"}`),
+	}
+
+	mps := bm.sharedstorage.(*sharedstoragemocks.Plugin)
+	mdi := bm.database.(*databasemocks.Plugin)
+
+	mps.On("UploadData", context.Background(), mock.Anything).Return("123", nil)
+	mdi.On("UpdateData", context.Background(), "ns1", data.ID, mock.Anything).Return(fmt.Errorf("pop"))
+
+	_, complete, err := bm.RunOperation(context.Background(), opUploadValue(op, data))
+
+	assert.False(t, complete)
+	assert.Regexp(t, "pop", err)
+
+	mps.AssertExpectations(t)
+	mdi.AssertExpectations(t)
+}
+
 func TestRunOperationUploadBlobUploadFail(t *testing.T) {
 	bm, cancel := newTestBroadcast(t)
 	defer cancel()
@@ -468,6 +590,27 @@ func TestRunOperationUploadBlobUploadFail(t *testing.T) {
 	mdx.AssertExpectations(t)
 }
 
+func TestRunOperationUploadValueUploadFail(t *testing.T) {
+	bm, cancel := newTestBroadcast(t)
+	defer cancel()
+
+	op := &core.Operation{}
+	data := &core.Data{
+		ID:    fftypes.NewUUID(),
+		Value: fftypes.JSONAnyPtr(`{"some":"data"}`),
+	}
+
+	mps := bm.sharedstorage.(*sharedstoragemocks.Plugin)
+	mps.On("UploadData", context.Background(), mock.Anything).Return("", fmt.Errorf("pop"))
+
+	_, complete, err := bm.RunOperation(context.Background(), opUploadValue(op, data))
+
+	assert.False(t, complete)
+	assert.Regexp(t, "pop", err)
+
+	mps.AssertExpectations(t)
+}
+
 func TestRunOperationUploadBlobDownloadFail(t *testing.T) {
 	bm, cancel := newTestBroadcast(t)
 	defer cancel()
diff --git a/internal/database/sqlcommon/data_sql_test.go b/internal/database/sqlcommon/data_sql_test.go
index 3e8a1f1b8..cd5265520 100644
--- a/internal/database/sqlcommon/data_sql_test.go
+++ b/internal/database/sqlcommon/data_sql_test.go
@@ -53,6 +53,7 @@ func TestDataE2EWithDB(t *testing.T) {
 		Hash:      fftypes.NewRandB32(),
 		Created:   fftypes.Now(),
 		Value:     fftypes.JSONAnyPtr(val.String()),
+		Public:    "some IPFS ref",
 		ValueSize: 12345,
 	}