ReportingCloud

Managing Templates

Topic

Every ReportingCloud account has a template storage, a private store on the service for the templates your application merges again and again. A merge request then names a stored template instead of carrying it. The templates endpoints of the Web API upload, list, inspect, download and delete these templates from code, so that a deployment script or an admin page of your application keeps the storage up to date. The portal does the same by hand, as Template Storage shows.

How the Template Storage Works

A stored template is known by its file name, such as sample_invoice.tx, the demo template that every trial account starts with. The endpoints that work with a stored template take this name in the query parameter templateName: POST /v1/document/merge merges it with data, POST /v1/document/findandreplace replaces text in it, GET /v1/templates/thumbnails renders its pages and GET /v1/document/share shares it.

  • Formats. The storage takes templates in the formats DOC, DOCX, RTF and TX, the TX Text Control format.
  • Unique names. Template names are not case-sensitive: Invoice.docx and invoice.DOCX name the same template. An upload under a name that already exists, in any case, replaces the stored template, which keeps its stored name. Check the name with GET /v1/templates/exists first when you want to keep the stored one.
  • Storage quota. An upload under a new name adds 1 to the template storage quota of your account, an upload that replaces a stored template doesn't, and deleting a template takes 1 off again. The plans hold from 10 templates (Basic) to 500 (Extreme), as Plans and Licensing lists.
  • Stored or sent with the request. A merge can also carry the template in its request body, where it is used once and not stored. A stored template is merged faster when you use it many times, because it is not transferred with every request. The Overview of the Mail Merge section compares the two ways.

The endpoints of the storage:

EndpointMethodWhat it doesSuccess response
templates/uploadPOSTStores the template of the body under templateName201 Created
templates/listGETLists the stored templates200, an array of objects with templateName, modified and size
templates/countGETCounts the stored templates200, a number
templates/existsGETChecks whether a template of this name is stored200, true or false
templates/infoGETReads the merge fields, merge blocks and user-defined document properties200, a TemplateInfo object
templates/pagecountGETCounts the pages of a template200, a number
templates/downloadGETReturns the template200, the template as a Base64 string in a JSON string
templates/deleteDELETEDeletes the template204 No Content

A name that is not in the storage gets 400 Bad Request from info, pagecount, download and delete, while exists answers false. An upload is refused with 400 when the extension of the name is not .doc, .docx, .rtf or .tx, or the template has a wrong format, isn't in the format of its extension or is corrupted. It is refused with 403 Forbidden when the storage of your plan is full, and the same 403 comes for a name of more than 207 characters or with # or /. An upload whose body is not a JSON string of valid Base64 data gets 500 Internal Server Error. Errors and Status Codes lists every cause.

Every request carries an Authorization header, as Authentication describes. The samples below read the API key from the environment variable REPORTINGCLOUD_API_KEY.

Upload a Template

The body of an upload is the template as a Base64 string in a JSON string, and templateName gives the name it gets in the storage. The following commands store the local file invoice.docx as invoice.docx:

# Wrap the Base64 string of the template in a JSON string
base64 -w 0 invoice.docx | jq -Rs . > template.json

curl -X POST "https://api.reporting.cloud/v1/templates/upload?templateName=invoice.docx" \
  -H "Authorization: ReportingCloud-APIKey $REPORTINGCLOUD_API_KEY" \
  -H "Content-Type: application/json" \
  --data-binary @template.json

The commands work as follows:

  • base64 -w 0 encodes the file without line breaks, and jq -Rs . turns the encoded text into a JSON string with quotes. The body is this string alone, not an object around it.
  • --data-binary @template.json sends the file unchanged, which keeps large templates out of the command line.
  • The answer 201 Created means the template is stored. A template stored under the same name before is replaced.

List and Count the Stored Templates

GET /v1/templates/list returns one object per stored template:

curl -H "Authorization: ReportingCloud-APIKey $REPORTINGCLOUD_API_KEY" \
  "https://api.reporting.cloud/v1/templates/list"
[
  {
    "templateName": "invoice.docx",
    "modified": "2016-06-02T09:25:22+00:00",
    "size": 3246
  },
  {
    "templateName": "sample_invoice.tx",
    "modified": "2019-06-28T07:22:34+00:00",
    "size": 35271
  }
]

modified is the time of the last change in UTC, and size the size of the template in bytes. When you need the number only, GET /v1/templates/count returns it as a plain number, such as 2.

How many templates your plan still takes is in the account settings: GET /v1/account/settings returns uploadedTemplates and maxTemplates along with the document and spell checking quotas.

Check Whether a Template Exists

Before a merge names a template, or before an upload would replace one, ask the storage whether the name is taken:

curl -H "Authorization: ReportingCloud-APIKey $REPORTINGCLOUD_API_KEY" \
  "https://api.reporting.cloud/v1/templates/exists?templateName=sample_invoice.tx"

The answer is true or false. Unlike the other endpoints that take a name, exists doesn't answer 400 for a name that is not stored.

Read the Merge Fields and Blocks of a Template

GET /v1/templates/info opens a stored template and returns its structure, without changing it. A merge field is a placeholder that the merge fills with the value of the JSON property of the same name, and a merge block is a part of the template that the merge repeats for each element of an array, such as a table row per invoice item:

curl -H "Authorization: ReportingCloud-APIKey $REPORTINGCLOUD_API_KEY" \
  "https://api.reporting.cloud/v1/templates/info?templateName=sample_invoice.tx"

The response for sample_invoice.tx, shortened to one field per level:

{
  "templateName": "sample_invoice.tx",
  "mergeBlocks": [
    {
      "name": "item",
      "mergeFields": [
        {
          "dateTimeFormat": "",
          "name": "qty",
          "numericFormat": "",
          "preserveFormatting": false,
          "text": "«qty»",
          "textAfter": "",
          "textBefore": ""
        }
      ],
      "mergeBlocks": []
    }
  ],
  "mergeFields": [
    {
      "dateTimeFormat": "",
      "name": "invoice_no",
      "numericFormat": "",
      "preserveFormatting": false,
      "text": "«invoice_no»",
      "textAfter": "",
      "textBefore": "#"
    }
  ],
  "userDocumentProperties": {}
}

The TemplateInfo object holds:

PropertyContent
templateNameThe template name as the request gives it, whose case can differ from the name in the storage
mergeFieldsThe merge fields outside of any block
mergeBlocksThe merge blocks, each with its name, its own mergeFields and the mergeBlocks nested in it
userDocumentPropertiesThe user-defined document properties, as names and values

Each merge field has a name, the formats dateTimeFormat and numericFormat it applies to date and numeric values, preserveFormatting, which says whether the formatting is preserved, its displayed text and the texts before and after the field, textBefore and textAfter. A block lists only its own fields and blocks, so walk the nested blocks to see the whole structure.

Your application can use the structure before it merges, for example to compare the field names with the keys of its JSON data, to check that it has an array for each block, or to build an input form with one field per merge field and merge the values the user enters. Merge Fields and Merge Blocks show how names and data match.

Count the Pages of a Template

GET /v1/templates/pagecount returns the number of pages of a stored template as a plain number:

curl -H "Authorization: ReportingCloud-APIKey $REPORTINGCLOUD_API_KEY" \
  "https://api.reporting.cloud/v1/templates/pagecount?templateName=sample_invoice.tx"

The number tells you, for example, which pages exist before you ask GET /v1/templates/thumbnails for images of a page range, as Thumbnails shows.

Download a Template

GET /v1/templates/download returns a stored template as a Base64 string in a JSON string. The following commands save sample_invoice.tx to a local file:

curl -H "Authorization: ReportingCloud-APIKey $REPORTINGCLOUD_API_KEY" \
  "https://api.reporting.cloud/v1/templates/download?templateName=sample_invoice.tx" \
  --output template.json

# Decode the Base64 string of the response
jq -r . template.json | base64 -d > sample_invoice.tx

jq -r . removes the quotes of the JSON string, and base64 -d decodes it into the file.

Delete a Template

DELETE /v1/templates/delete removes a template from the storage and frees its place in the template storage quota:

curl -X DELETE -H "Authorization: ReportingCloud-APIKey $REPORTINGCLOUD_API_KEY" \
  "https://api.reporting.cloud/v1/templates/delete?templateName=invoice.docx"

The answer 204 No Content has no body. A merge that names the deleted template afterward is refused with 400 Bad Request.

Manage Templates With .NET

The NuGet package TXTextControl.ReportingCloud sends the same requests from C#. The following console application uploads invoice.docx unless the storage already has a template of this name, lists the stored templates and prints the merge structure of the uploaded one:

using TXTextControl.ReportingCloud;

var rc = new ReportingCloud(Environment.GetEnvironmentVariable("REPORTINGCLOUD_API_KEY"));

// Upload the template only if the storage has none of this name
if (!rc.TemplateExists("invoice.docx"))
{
    rc.UploadTemplate("invoice.docx", File.ReadAllBytes("invoice.docx"));
}

foreach (Template template in rc.ListTemplates())
{
    Console.WriteLine($"{template.TemplateName}: {template.Size} bytes, changed {template.Modified}");
}

TemplateInfo info = rc.GetTemplateInfo("invoice.docx");

foreach (MergeField field in info.MergeFields)
{
    Console.WriteLine($"Field: {field.Name}");
}

foreach (MergeBlock block in info.MergeBlocks)
{
    PrintBlock(block, 0);
}

// Prints a block with its fields and, indented, the blocks nested in it
void PrintBlock(MergeBlock block, int level)
{
    string indent = new string(' ', level * 2);
    Console.WriteLine($"{indent}Block: {block.Name}");

    foreach (MergeField field in block.MergeFields)
    {
        Console.WriteLine($"{indent}  Field: {field.Name}");
    }

    foreach (MergeBlock nested in block.MergeBlocks)
    {
        PrintBlock(nested, level + 1);
    }
}

The code works as follows:

  • The ReportingCloud constructor takes the API key. The class sends it in the Authorization header of every request to https://api.reporting.cloud.
  • TemplateExists and UploadTemplate call GET /v1/templates/exists and POST /v1/templates/upload. UploadTemplate takes the name in the storage and the template as a byte array, and does the Base64 encoding.
  • ListTemplates returns a list of Template objects with TemplateName, Modified and Size.
  • GetTemplateInfo returns a TemplateInfo object whose MergeFields and MergeBlocks hold MergeField and MergeBlock objects, so PrintBlock calls itself for each nested block.

Each endpoint of the storage has a method in the .NET and the PHP SDK:

Endpoint.NETPHP
templates/uploadUploadTemplate(templateName, template)uploadTemplate($templateFilename), uploadTemplateFromBase64($data, $templateName)
templates/listListTemplates()getTemplateList()
templates/countGetTemplateCount()getTemplateCount()
templates/existsTemplateExists(templateName)templateExists($templateName)
templates/infoGetTemplateInfo(templateName)getTemplateInfo($templateName)
templates/pagecountGetTemplatePageCount(templateName)getTemplatePageCount($templateName)
templates/downloadDownloadTemplate(templateName)downloadTemplate($templateName)
templates/deleteDeleteTemplate(templateName)deleteTemplate($templateName)

A refused request makes the .NET methods throw an ArgumentException and the PHP methods a RuntimeException, as Handle Errors in the SDKs describes. .NET and PHP in the SDKs section show how to install and set up each SDK.

Next Steps