Getting Started With Temper

This Getting Started guide is intended to serve as both a tutorial and source of documentation for Temper. Topics covered include:


Download Temper and Xojo

To get started, download the latest version of Temper and unzip the file. The zip file contains the Temper Xojo project, a sample database, and files that contain sample API calls.

If you don't already have Xojo, you'll need to download that as well.


Enable XML Custom Web Publishing

If you want to follow along with this tutorial, upload the sample database to your FileMaker Server instance. You'll also need to enable XML Custom Web Publishing on your server.

If you are running FileMaker Server 17, you will need to use the command line interface to enable XML. The command to run is:

fmsadmin SET CWPCONFIG enablexml=true

Open the Temper Xojo Project

Double-click the Temper Xojo project file (Temper.xojo_binary_project). The project should open in the Xojo IDE, and look something like this.


Run Temper

Click the Run button in the Xojo IDE toolbar. Temper will begin running in debug mode and is ready for API calls.


About The Example API Calls

In the examples that follow, the API calls are being made against a local instance of FileMaker Server. Therefore, the "Host" value in the API calls is: localhost. Be sure to adjust your API calls so that the Host value represents the address of your FileMaker Server instance.

In addition, the instance of FileMaker Server that is being used in this tutorial is not using an SSL certificate. Therefore, the "Protocol" value being used is: http. If your server is using an SSL certificate, adjust your API calls so that the Protocol being used in the requests is: https.

And finally, if you are making API calls against a database other than the sample database, adjust the Database, Account, and Password values accordingly.


The Layout Parameter

When making Temper API calls, the Layout parameter determines the layout - and therefore, the table - that the API call is made against. It is the layout's base table that will be used by FileMaker Server to resolve the request.

The Layout also determines what fields, portals, and value lists are available to the API call. If you reference a layout that only includes a subset of a table's fields, then only those fields will be returned by API calls that retrieve records. Also, only those fields that appear on that layout can be set when adding or updating records.


Getting A Specific Record

If you know the internal RecordID of a record that you want to retrieve, you can make a "GetRecord" API request. For example:

curl -X "POST" "http://127.0.0.1:8080" \
     -H 'Content-Type: application/json' \
     -d $'{
  "Layout": "Store Details",
  "Action": "GetRecord",
  "RecordID": 7879,
  "Host": "localhost",
  "Account": "xfm",
  "Protocol": "http",
  "Password": "latte",
  "Database": "starbucks-us"
}'

The response that you will get will be something like this:

{
  "fmresultset": {
    "resultset": {
      "count": "1",
      "fetch-size": "1",
      "record": [
        {
          "mod-id": "1",
          "record-id": "7879",
          "fields": {
            "Store ID": "16642",
            "Store Name": "Marriott Desert Springs",
            "Street 1": "74855 Country Club Dr",
            "Street 2": "",
            "Street 3": "",
            "City": "Palm Desert",
            "State": "CA",
            "Postal Code": "92260",
            "Phone Number": "760-341-2211",
            "Latitude": "33.755527496337891",
            "Longitude": "-116.36180114746094",
            "Comments": "",
            "Photo": ""
          }
        }
      ]
    },
    "error": {
      "code": "0"
    }
  }
}

Getting A Random Record

If you would like to get a random record, you can make a "FindAnyRecord" API request. For example:

curl -X "POST" "http://127.0.0.1:8080" \
     -H 'Content-Type: application/json' \
     -d $'{
  "Layout": "Store Details",
  "Action": "FindAnyRecord",
  "Host": "localhost",
  "Account": "xfm",
  "Protocol": "http",
  "Password": "latte",
  "Database": "starbucks-us"
}'

The response that you will get will be something like this:

{
  "fmresultset": {
    "resultset": {
      "count": "1",
      "fetch-size": "1",
      "record": [
        {
          "mod-id": "1",
          "record-id": "10162",
          "fields": {
            "Store ID": "91390",
            "Store Name": "College & Pacific - Lacey",
            "Street 1": "1110 College Street SE",
            "Street 2": "",
            "Street 3": "",
            "City": "Lacey",
            "State": "WA",
            "Postal Code": "98503",
            "Phone Number": "360-456-1891",
            "Latitude": "47.037693023681641",
            "Longitude": "-122.82286071777344",
            "Comments": "",
            "Photo": ""
          }
        }
      ]
    },
    "error": {
      "code": "0"
    }
  }
}

Getting All Records

If you would like to get all records, you can make a "FindAllRecords" API request. For example:

curl -X "POST" "http://127.0.0.1:8080" \
     -H 'Content-Type: application/json' \
     -d $'{
  "Layout": "Store Details",
  "Action": "FindAllRecords",
  "Host": "localhost",
  "Account": "xfm",
  "Protocol": "http",
  "Password": "latte",
  "Database": "starbucks-us"
}'

Keep in mind that FindAllRecords requests have the potential to return a lot of data, so you should make these requests carefully. See the "Pagination" section below for information on how to retrieve large groups of records in batches.


Finding Records

If you would like to find records based on specific criteria, you can make a "FindRecords" request.

For example, suppose that you want to find all records in the state of Virginia. Your API call might look like this:

curl -X "POST" "http://127.0.0.1:8080" \
     -H 'Content-Type: application/json' \
     -d $'{
  "Layout": "Store List",
  "Action": "FindRecords",
  "Requests": [
    {
      "Fields": {
        "State": "VA"
      },
      "Type": "find"
    }
  ],
  "Host": "localhost",
  "Account": "xfm",
  "Protocol": "http",
  "Password": "latte",
  "Database": "starbucks-us"
}'

Notice that the API call includes the Requests parameter, which accepts an array of one or more find requests. In the example above, we've specified a single find (include) request.

If we wanted to find all records where the state is Virginia and the city is Richmond, we would make a call like this:

curl -X "POST" "http://127.0.0.1:8080" \
     -H 'Content-Type: application/json' \
     -d $'{
  "Layout": "Store List",
  "Action": "FindRecords",
  "Requests": [
    {
      "Fields": {
        "State": "VA",
        "City": "Richmond"
      },
      "Type": "find"
    }
  ],
  "Host": "localhost",
  "Account": "xfm",
  "Protocol": "http",
  "Password": "latte",
  "Database": "starbucks-us"
}'

You can also perform finds that omit records. Continuing with the example above, if we wanted to find all records where the state is Virginia and omit records where the city is Richmond, we would make a call like this:

## Find Records - VA Example
curl -X "POST" "http://127.0.0.1:8080" \
     -H 'Content-Type: application/json' \
     -d $'{
  "Layout": "Store List",
  "Action": "FindRecords",
  "Requests": [
    {
      "Fields": {
        "State": "VA"
      },
      "Type": "find"
    },
    {
      "Fields": {
        "City": "Richmond"
      },
      "Type": "omit"
    }
  ],
  "Host": "localhost",
  "Account": "xfm",
  "Protocol": "http",
  "Password": "latte",
  "Database": "starbucks-us"
}'

Notice that we've added a second request to the "Requests" parameter, and that its Type is "omit."


Pagination

If you are making API calls that have the potential to return large numbers of records, you might want to consider "paginating" the results. In other words, make requests that return records in batches. Temper supports pagination via two parameters: Max and Skip.

With the Max parameter, you specify the maximum number of records to be returned, regardless of how many records were actually found. With the Skip parameter, you indicate how many records in the found set should be skipped.

For example, to make a FindAllRecords request that retrieves a maximum of 50 records at a time and skips the first 50 records, you would make a request like this:

curl -X "POST" "http://127.0.0.1:8080" \
     -H 'Content-Type: application/json' \
     -d $'{
  "Layout": "Store List",
  "Action": "FindAllRecords",
  "Max": "50",
  "Host": "localhost",
  "Account": "xfm",
  "Protocol": "http",
  "Password": "latte",
  "Database": "starbucks-us",
  "Skip": "50"
}'

Sorting

You'll often want records returned in a specific order. Temper supports this via the SortFields parameter.

For example, suppose that you want to make a FindAllRecords request that retrieves records sorted by one field in ascending order, and sub-sorted by a second field in descending order. The request might look like this:

curl -X "POST" "http://127.0.0.1:8080" \
     -H 'Content-Type: application/json' \
     -d $'{
  "Layout": "Store List",
  "Action": "FindAllRecords",
  "SortFields": [
    {
      "FieldName": "State",
      "SortOrder": "Ascend"
    },
    {
      "FieldName": "City",
      "SortOrder": "Descend"
    }
  ],
  "Host": "localhost",
  "Account": "xfm",
  "Protocol": "http",
  "Password": "latte",
  "Database": "starbucks-us"
}'

You can combine SortFields with the Max and Skip parameters. To retrieve a batch of records in sorted order, a request might look like this:

curl -X "POST" "http://127.0.0.1:8080" \
     -H 'Content-Type: application/json' \
     -d $'{
  "Layout": "Store List",
  "Action": "FindAllRecords",
  "SortFields": [
    {
      "FieldName": "State",
      "SortOrder": "Ascend"
    },
    {
      "FieldName": "City",
      "SortOrder": "Descend"
    }
  ],
  "Max": "50",
  "Host": "localhost",
  "Account": "xfm",
  "Protocol": "http",
  "Password": "latte",
  "Database": "starbucks-us",
  "Skip": "50"
}'

Adding A Record

To add a record, make an InsertRecord request, and specify the fields that you want to set and their values. The request might look like this:

curl -X "POST" "http://127.0.0.1:8080" \
     -H 'Content-Type: application/json' \
     -d $'{
  "Layout": "Store Details",
  "Action": "InsertRecord",
  "Fields": {
    "Store Name": "Test",
    "Street 1": "100 Test Avenue",
    "City": "Columbus",
    "State": "OH"
  },
  "Host": "localhost",
  "Account": "xfm",
  "Protocol": "http",
  "Password": "latte",
  "Database": "starbucks-us"
}'

Temper will return a JSON representation of the record that was created. Here's a response to the API request.

{
  "fmresultset": {
    "resultset": {
      "count": "1",
      "fetch-size": "1",
      "record": [
        {
          "mod-id": "0",
          "record-id": "13227",
          "fields": {
            "Store ID": "",
            "Store Name": "Test",
            "Street 1": "100 Test Avenue",
            "Street 2": "",
            "Street 3": "",
            "City": "Columbus",
            "State": "OH",
            "Postal Code": "",
            "Phone Number": "",
            "Latitude": "",
            "Longitude": "",
            "Comments": "",
            "Photo": ""
          }
        }
      ]
    },
    "error": {
      "code": "0"
    }
  }
}

Updating A Record

To update a record, make an UpdateRecord request, specify the RecordID of the record that you want to update, as well as the fields that you want to set and their values. The request might look like this:

curl -X "POST" "http://127.0.0.1:8080" \
     -H 'Content-Type: application/json' \
     -d $'{
  "Layout": "Store List",
  "Action": "UpdateRecord",
  "Fields": {
    "Store Name": "Test Updated",
    "Store ID": "Updated"
  },
  "RecordID": "13227",
  "Host": "localhost",
  "Account": "xfm",
  "Protocol": "http",
  "Password": "latte",
  "Database": "starbucks-us"
}'

Temper will return a JSON representation of the record that was updated. Here's a response to the API request.

{
  "fmresultset": {
    "resultset": {
      "count": "1",
      "fetch-size": "1",
      "record": [
        {
          "mod-id": "1",
          "record-id": "13227",
          "fields": {
            "Store ID": "Updated",
            "Store Name": "Test Updated",
            "Street 1": "100 Test Avenue",
            "Street 2": "",
            "Street 3": "",
            "City": "Columbus",
            "State": "OH",
            "Postal Code": "",
            "Phone Number": "",
            "Latitude": "",
            "Longitude": ""
          }
        }
      ]
    },
    "error": {
      "code": "0"
    }
  }
}

Deleting A Record

To delete a record, make a DeleteRecord request and specify the RecordID of the record that you want to delete. The request might look like this:

curl -X "POST" "http://127.0.0.1:8080" \
     -H 'Content-Type: application/json' \
     -d $'{
  "Layout": "Store Details",
  "Action": "DeleteRecord",
  "RecordID": 13227,
  "Host": "localhost",
  "Account": "xfm",
  "Protocol": "http",
  "Password": "latte",
  "Database": "starbucks-us"
}'

Unlike InsertRecord and UpdateRecord requests, Temper will not return a JSON representation of the record that was involved in the request (deleted). However, you can determine if the deletion was successful by examining the error value in the response. If the deletion was successful, the error code will be 0. Here's a response to the API request.

{
  "fmresultset": {
    "resultset": {
      "count": "0",
      "fetch-size": "0"
    },
    "error": {
      "code": "0"
    }
  }
}

Getting A Container's Contents

To retrieve the contents of a container field, make a GetContainer request. The request might look like this:

curl -X "POST" "http://127.0.0.1:8080" \
     -H 'Content-Type: application/json' \
     -d $'{
  "Action": "GetContainer",
  "Host": "localhost",
  "Account": "xfm",
  "ContainerURL": "/fmi/xml/cnt/500px-Albertsons_(logo).svg.png?-db=starbucks-us&-lay=Store%20Details&-recid=12898&-field=Photo(1)",
  "Protocol": "http",
  "Password": "latte",
  "Database": "starbucks-us"
}'

Notice that the request includes a ContainerURL. This is the URL that is returned in the responses to Temper GetRecord, FindAnyRecord, FindAllRecords, and FindRecords requests. In other words, when making those types of requests, if a container is on the layout that was specified, the value of the container field will be the URL that you can use to make a GetContainer request.

If successful, the response to a GetContainer will be the contents of the file in the container. Temper will automatically set the Content-Type of the file.


Setting A Container's Contents

Setting the contents of a container field is a little tricky, in that you must first have the file that you want to use uploaded to a location in which it is accessible via a URL. How you do this will depend on the type of application that you are developing.

Once you have the file uploaded, and are ready to store it in a container, you make a GetRecord request based on the record that you want the file to be associated with. In addition, you add a ScriptPreSort parameter to the Temper request, and its value is the name of a script in the database that will actually retrieve the file. And finally, you also add a ScriptPreSortParam parameter where its value is the actual URL to the file that is to be stored.

The request might look like this:

curl -X "POST" "http://127.0.0.1:8080" \
     -H 'Content-Type: application/json' \
     -d $'{
  "Layout": "Store Details",
  "Action": "GetRecord",
  "RecordID": 12898,
  "Host": "localhost",
  "Account": "xfm",
  "ScriptPreSort": "Photo Container Set",
  "Protocol": "http",
  "Password": "latte",
  "ScriptPreSortParam": "https://upload.wikimedia.org/wikipedia/commons/thumb/b/b6/Albertsons_%28logo%29.svg/500px-Albertsons_%28logo%29.svg.png",
  "Database": "starbucks-us"
}'

In this request, we're using a GetRecord request to specify the record that the file is to be associated with. We're using a ScriptPreSort parameter to indicate that the "Photo Container Set" script should be called to retrieve the file. And we're using a ScriptPreSortParam parameter to specify the location (URL) of the file to be stored in the container. In this case, we're using an image that is stored on the wikimedia.org Web site.

If successful, the response to the request will be the JSON representation of the record whose container was set. For example:

{
  "fmresultset": {
    "resultset": {
      "count": "1",
      "fetch-size": "1",
      "record": [
        {
          "mod-id": "4",
          "record-id": "12898",
          "fields": {
            "Store ID": "1015248",
            "Store Name": "Albertsons - Rio Rancho 923",
            "Street 1": "7800 Enchanted Hills Blvd NE",
            "Street 2": "",
            "Street 3": "",
            "City": "Rio Rancho",
            "State": "NM",
            "Postal Code": "87144",
            "Phone Number": "505-771-7082",
            "Latitude": "35.323337554931641",
            "Longitude": "-106.57643890380859",
            "Comments": "",
            "Photo": "\/fmi\/xml\/cnt\/500px-Albertsons_(logo).svg.png?-db=starbucks-us&-lay=Store%20Details&-recid=12898&-field=Photo(1)"
          }
        }
      ]
    },
    "error": {
      "code": "0"
    }
  }
}

Getting Layout Names

Temper also provides several request types that can be used to get meta information about the database. For example, to get a list of the layouts that are available, you would make GetLayoutNames request:

curl -X "POST" "http://127.0.0.1:8080" \
     -H 'Content-Type: application/json' \
     -d $'{
  "Protocol": "http",
  "Host": "localhost",
  "Database": "starbucks-us",
  "Account": "xfm",
  "Password": "latte",
  "Action": "GetLayoutNames"
}'

The response that you will get will be something like this:

{
  "fmresultset": {
    "resultset": {
      "count": "2",
      "fetch-size": "2",
      "record": [
        {
          "mod-id": "11",
          "record-id": "1",
          "fields": {
            "LAYOUT_NAME": "Store List"
          }
        },
        {
          "mod-id": "16",
          "record-id": "3",
          "fields": {
            "LAYOUT_NAME": "Store Details"
          }
        }
      ]
    },
    "error": {
      "code": "0"
    }
  }
}

Getting Layout Details

To get detailed information about a layout, you can make a GetLayoutView request.

curl -X "POST" "http://127.0.0.1:8080" \
     -H 'Content-Type: application/json' \
     -d $'{
  "Layout": "Store List",
  "Action": "GetLayoutView",
  "Host": "localhost",
  "Account": "xfm",
  "Protocol": "http",
  "Password": "latte",
  "Database": "starbucks-us"
}'

The response that you will get will be something like this:

{
  "FMPXMLLAYOUT": {
    "ERRORCODE": "0",
    "PRODUCT": {
      "BUILD": "04\/10\/2018",
      "NAME": "FileMaker Web Publishing Engine",
      "VERSION": "17.0.1.146"
    },
    "LAYOUT": {
      "DATABASE": "starbucks-us",
      "NAME": "Store List",
      "FIELD": [
        {
          "NAME": "Store ID",
          "STYLE": {
            "TYPE": "EDITTEXT",
            "VALUELIST": ""
          }
        },
        {
          "NAME": "Store Name",
          "STYLE": {
            "TYPE": "EDITTEXT",
            "VALUELIST": ""
          }
        },
        {
          "NAME": "Street 1",
          "STYLE": {
            "TYPE": "EDITTEXT",
            "VALUELIST": ""
          }
        },
        {
          "NAME": "Street 2",
          "STYLE": {
            "TYPE": "EDITTEXT",
            "VALUELIST": ""
          }
        },
        {
          "NAME": "Street 3",
          "STYLE": {
            "TYPE": "EDITTEXT",
            "VALUELIST": ""
          }
        },
        {
          "NAME": "City",
          "STYLE": {
            "TYPE": "EDITTEXT",
            "VALUELIST": ""
          }
        },
        {
          "NAME": "State",
          "STYLE": {
            "TYPE": "EDITTEXT",
            "VALUELIST": ""
          }
        },
        {
          "NAME": "Postal Code",
          "STYLE": {
            "TYPE": "EDITTEXT",
            "VALUELIST": ""
          }
        },
        {
          "NAME": "Phone Number",
          "STYLE": {
            "TYPE": "EDITTEXT",
            "VALUELIST": ""
          }
        },
        {
          "NAME": "Latitude",
          "STYLE": {
            "TYPE": "EDITTEXT",
            "VALUELIST": ""
          }
        },
        {
          "NAME": "Longitude",
          "STYLE": {
            "TYPE": "EDITTEXT",
            "VALUELIST": ""
          }
        }
      ]
    },
    "VALUELISTS": ""
  }
}

Getting Script Names

To get a list of the scripts that are available, you would make GetScriptNames request:

curl -X "POST" "http://127.0.0.1:8080" \
     -H 'Content-Type: application/json' \
     -d $'{
  "Protocol": "http",
  "Host": "localhost",
  "Database": "starbucks-us",
  "Account": "xfm",
  "Password": "latte",
  "Action": "GetScriptNames"
}'

The response that will look like this:

{
  "fmresultset": {
    "resultset": {
      "count": "1",
      "fetch-size": "1",
      "record": [
        {
          "mod-id": "0",
          "record-id": "0",
          "fields": {
            "SCRIPT_NAME": "Photo Container Set"
          }
        }
      ]
    },
    "error": {
      "code": "0"
    }
  }
}

Getting Value Lists

To get value lists, you can either make a GetLayoutView request, or a GetValueLists request. When making a GetValueLists request, you specify a Layout, and any value lists used on that layout will be returned. For example:

curl -X "POST" "http://127.0.0.1:8080" \
     -H 'Content-Type: application/json' \
     -d $'{
  "Layout": "Store Details",
  "Action": "GetValueLists",
  "Host": "localhost",
  "Account": "xfm",
  "Protocol": "http",
  "Password": "latte",
  "Database": "starbucks-us"
}'

The response that will look like this:

{
  "Cities": [
    {
      "DISPLAY": "Aberdeen",
      "text": "Aberdeen"
    },
    {
      "DISPLAY": "Abilene",
      "text": "Abilene"
    },
    {
      "DISPLAY": "Abingdon",
      "text": "Abingdon"
    },
	...
  ],
  "States": [
    {
      "DISPLAY": "AK",
      "text": "AK"
    },
    {
      "DISPLAY": "AL",
      "text": "AL"
    },
	...
  ]
}

Server Options

At the heart of Temper is Aloe Micro, a Xojo-based Web server module. Aloe Micro is a variant of Aloe Express, a popular Xojo Web server module. Aloe Micro handles the incoming API requests and routes them to the Temper module for processing. You can adjust the configuration of Aloe Micro to meet your specific needs, including specifying the port that Temper is listening on, whether or not it is listening for HTTPS requests, and so on. In this section, we review some of the most popular options.

By default, Temper listens for HTTP requests on port 8080. You can change the port by specifying a port in the app itself, or by using a command line argument. To change the port in the app itself, modify the App.Run event handler, like this:

// Create an instance of AloeMicro.Server, and configure it with optional command-line arguments.
Server = New AloeMicro.Server(args)

// Change the port.
Server.Port = 80

// Rename the server.
Server.Name = "Temper API Server " + Temper.VersionString

// Start the server.
Server.Start

To change the port from the command line, use the "--port" argument. For example: --port=64000

For additional server configuration options, see the documentation for the Aloe Express "Server" class, available here: https://aloe.zone/resources/docs/classes.html The Aloe Express class is very similar to the Server class in Aloe Micro.