Inbound API


Description

Helium provides inbound API functionality for DSL applications. Using this, a function can be annotated as a REST API function which can then be invoked from an external client. Functions can be annotated with @POST, @DELETE, @PUT or @GET. In addition, and if the API function returns a custom object or custom object collection, the @ResponseExclude and @ResponseExpand annotations can be used to specify which attributes should be excluded from the returned values and which relationships should be expanded in the returned values.



Supported API Function Return Types


ObjectObject Collectionjsonjsonarrayvoidother primitives
@POSTYesYesYesYesYesNo
@PUTYesYesYesYesYesNo
@GETYesYesYesYesNoNo
@DELETEYesYesYesYesYesNo

In summary to the above:

  • Custom object, custom object collections, json, jsonarray and void are valid return types for all API functions with the exception of GET functions, which do not support void.
  • GET functions cannot have a return type of void. 
  • Primitive values, other than json and jsonarray are not supported as valid return types for any API function.



Supported API Function Parameter Types


ObjectObject CollectionjsonjsonarrayPath ParametersNo ParamatersQuery Parameters
@POSTYesYesYesYesYesYesYes
@PUTYesYesYesYesYesYesYes
@GETNoNoNoNoYesYesYes
@DELETENoNoNoNoYesYesYes

In summary to the above:

  • All API functions can have path and query parameters.
  • PUT and POST functions support custom object, custom object collection, json and jsonarray  body parameters. Only one of these parameters is allowed per API function. In other words only one parameter representing the payload body is allowed.
  • GET and DELETE API functions do not support any parameter representing a body. Only path and query parameters are supported in this case.
  • Parameters are not compulsory for any API function.



API Response Codes

Default Response Codes

HTTP response codes are issued by the server in response to the request made. The codes can generally be used to determine whether the request was successful or not. Helium implements its own default responses for inbound API requests. There are described below.

  • 400 Bad request: Could be result of incorrect arguments being sent when invoking the API.
  • 200 Success: Applicable to all API function types. No error has occurred and API function executed successfully.
  • 204 Success with no content: No error has occurred and API function executed successfully. Applicable to function with void return type.
  • 404 Not found: The called URL could not be routed to an API function. A null value was returned from a GET API function.

Custom Response Codes

In addition to the above, Helium also allows developers to override the default response codes to an extent. This can be achieved by using the api:setStatusCode  built-in function and allows developers to use application logic to determine what response codes should be returned.

Note that if Helium encounters an exception before reaching the actual execution of the inbound API function the default error codes will apply. For example if the posted arguments cannot be parsed by Helium, it will result in a 400 response despite any logic in the inbound API function. Similarly if a API call is made that cannot be routed to an inbound API function helium will generate a 404 response.

api:setStatusCode is therefore mostly relevant for overriding response codes for what Helium considers success responses by default.

See the Example 6 for a detailed example.



Format for API Function Body Arguments


Native JSON Body Arguments

The JSON body being posted should correspond to the function body parameter. If the function body parameter is of type json, a JSON object should be posted. If the function body parameter is of type jsonarray, a JSON array should be posted.



Custom Object and Custom Object Collection Body Arguments

The JSON body being posted should correspond to the function body parameter. If the parameter is a collection, a JSON array should be posted. If the parameter is an object, a JSON object should be posted. The JSON fields names should correspond to the DSL object attribute names. The values specified in the JSON data being posted should correspond to the DSL object attributes as follows:

  • string attribute values are to be specified as strings, contained inside quotes
  • int attribute values are to be specified as numbers without any decimal parts
  • enum attribute values are to be specified as if they are strings, using only the enum value not including the enum type
  • For persistent objects, an _id field has to be specified. The value for the field should be a string representing a valid UUID. The value will be used as the unique identifier for the persistent object instance.
  • For non-persistent objects an _id field does not need to be specified but is allowed. If a value for _id is specified it will be used as the unique identifier for the non-persistent object instance.
  • date and datetime attribute values are to be represented by a numeric value representing the number of milliseconds since the unix epoch.
  • blob values are to be posted as base64 encoded string of file content byte arrays.
  • In addition to blob content as specified above, meta data should also be included for blob attributes. The name for these fields should be the name of the blob attributes appended with a specific key representing the type of meta data. Consider a case where our blob attributes name is "data". Values for the following fields should then also be posted:
    • data_fname__ is a sting field that represents the file name for the blob
    • data_size__ is an integer field that represents the file size for the blob
    • data_mtype__ is a sting field that represents the mime type for the blob
  • Values for relationships can be posted using a string representation of the unique identifier of the related object and using a field name that corresponds to the relationship name on the DSL object. Posting values for many to many relationships is not supported.



Path Parameters

Path parameters can be specified as part of the API path by surrounding the parameter name with curly brackets when defining the API path inside the relevant API annotation. The part of the API path referencing a path parameter should contain only the single path parameter surrounded by curly brackets and no additional literal values or parameters. In addition to referencing a path parameter in the API path itself, path parameters should also be represented by a matching function parameter. Path parameters and their associated function parameters can be represented by enum types and primitive types with the exception of blob, json and jsonarray. A recommended convention is to also describe the path parameter as part of the API path.

The following example demonstrates the points mentioned above:

@GET("v1/farmer/farmerId/{farmerId}")
Farmer getFarmerById(uuid farmerId) {
...
}
curl -v \
-u "user:pass" \
-X GET https://dev.mezzanineware.com/rest/mezzanine-extended-inbound-api-test/v1/farmer/farmerId/0d15a498-6a40-4d7a-a895-e3dde03598cc | python -m json.tool

Values for path parameters are converted similarly to how fields for JSON body arguments are converted. See the above section for reference. If an argument value sent for a path parameter cannot be converted to the type of the matching function parameter a 400 error code will be generated with an appropriate error message.



Query Parameters

Any function parameter specified for an API function, that does not represent a path parameter, can be used as a query parameter. Similarly to path parameters, query parameters can be represented by enum types and primitive types with the exception of blob, json and jsonarray. All query parameters are treated as optional by Helium. This means that Helium will not validate that values for any query parameter are specified when an API is invoked. If a value for query parameter is not specified, it's value in the API function will be null.

The following example demonstrates the points mentioned above:

@GET("v1/purchase/latest")
Purchase[] getLatestPurchases(int limit, bool showReturns) {
...
} 
 curl -v \
-u "user:pass" \
-X GET "https://dev.mezzanineware.com/rest/mezzanine-extended-inbound-api-test/v1/purchase/latest?limit=20&showReturns=false" | python -m json.tool

Note in the above example how the start of the query string is denoted by '?' and how sections in the query string, each representing a different query parameter and argument value, are separated by '&'.

Values for query parameters are converted similarly to how path parameters and fields for JSON body arguments are converted. See the above sections for reference. If an argument value sent for a query parameter cannot be converted to the type of the matching function parameter a 400 error code will be returned with an appropriate error message. If a parameter is referenced in the query string and that parameter was not defined as a query parameter in the API function, a 400 error code with appropriate error message will also be returned by the API.



@ResponseExclude and @ResponseInclude

When returning a custom object or custom object collection from an API function the default behaviour for the response values will be as follows:

  • All fields that represent attributes are included by default.
  • For both persistent and non-persistent objects and collections the unique identifier for the object, as used by Helium, is included as an _id field.
  • For relationships that represent many to one and one to one multiplicities, the unique identifier of the related object is included with the relationship name as the field.
  • Relationships that represent one to many are excluded from the results by default.

Consider the following example showing the default behaviour:

persistent object Person {
    string name;
    string surname;
    string mobileNumber;
    
	@OneToMany 
    Pet pets via owner;
}
 
persistent object Pet {
    string name;
    string age;
    PET_TYPE type;
}

enum PET_TYPE {
    Dog,
    Molerat
}
@GET("v1/person/mobileNumber/{mobileNumber}")
Person getPerson(string mobileNumber) {
...
}

The above model and API function results in the following result when invoking the API:

 {
	"_id": "00e7b7e1-8506-4043-b4f3-fb29220540d4",
    "mobileNumber": "27761231234",
    "name": "Jack",
    "surname": "Marques"
}

The default behaviour as shown above can be overridden by making use of the @ResponseExclude and @ResponseExpand annotations in order to exclude an attribute or relationships for the return values or to expand a relationship in the return value. The paths specified for both @ResponseExclude and @ResponseExpand must represent valid attribute and relationship paths but the paths can be chained to, for example, expand on a relationship and to then exclude a field for the object represented in the expanded relationship:


@ResponseExclude("_id")
@ResponseExclude("mobileNumber")
@ResponseExpand("pets")
@ResponseExclude("pets.age")
@GET("v1/person/mobileNumber/{mobileNumber}")
Person getPerson(string mobileNumber) {
...
}

The modified API function above results in the following return value:

 {
    "name": "Jack",
    "pets": [
        {
			"_id": "ac33a973-6c86-479f-8236-7c71b52b0c2c",
            "name": "Jasmine",
            "owner": "d90b5f26-693d-40d3-abeb-fb028a6bbdee",
            "type": "Dog"
        },
        {
			"_id": "c81b4856-35d3-4e15-8ee4-ca1ec500af81",
            "name": "Markus",
            "owner": "d90b5f26-693d-40d3-abeb-fb028a6bbdee",
            "type": "Dog"
        }
    ],
    "surname": "Marques"
}

More examples of this can be seen here.



API URL

The URL for Helium APIs contains the following sections:


Section
Example
Description
Base URL
https://dev.mezzanineware.com/rest
The base URL for inbound API REST functionality. This will differ accordingly depending on which server is being used. In this case, its

dev.mezzanineware.com. In cases where the app for which the API is being invoked has a fully qualified domain name set, it can be used instead of the server URL. For example

https://myapp.heliumapps.com/rest/
API friendly app name
mezzanine-extended-inbound-api-test
 The second part represents to API friendly name. The value is needs to be set an app when creating the app on the Helium Core web application or by updating the app on the Helium Core web application.
 Resource
v1/purchase/latest

The final part of the URL refers to the value specified when annotating an function as an inbound REST API post function. Note that we add v1 as a versioning convention to indicate that this is version 1 of our API. Using this, development on a new version of an API can happen seamlessly while still honoring the original API contract as it is being used by existing clients.




Gotchas, Conventions and Best Practices

  • Version your REST API resources as described in this document for example v1/myresouce.
  • When defining REST resource paths follow proper conventions to indicate the inherent relationships between entities being interacted with. For example v1/farmer/profile/documentation.

  • Don't use verbs such as get, post, delete or put in your API paths. The intent of a specific API should be implied by the type of API, such as GET, PUT, DELETE, POST and the entities and relationships implied from the API path.

  • Post functionality as implemented in the Helium DSL is strictly that of object creation when posting persistent object data. This means that if two API calls are made to post persistent data for the same object type and using the same value for _id, the API call will fail due to a duplicate key violation.

  • Put functionality as implemented in the Helium DSL is strictly that of update when putting persistent object data. If an object instance is to be created, post should be used and if an object instance is to be updated, put is to be used.
  • For POST or PUT API functions with persistent object or collections as a body parameter, the value for _id, of each object instance, must be specified when invoking the API.
  • For POST or PUT API functions with non-persistent object or collections as a body parameter, the value for _id, of each object instance, can be specified when invoking the API but is not required.



Examples


Example 1

This example highlights the following:

  • Usage of the @POST annotation.
  • How a persistent object or persistent object collection can be posted.
  • The use of a non-persistent object as an API function return type.


Model response object
object ApiResponse {
    datetime requestReceived;
    datetime requestProcessed;
    string message;
    bool success;
}
Model body parameter object
persistent object StockUpdate {
    string stockName;
    int level;
    decimal price;
    date stocktakeDate;
 
    @ManyToOne
    Shop shop via stockUpdates;
     
    @ManyToOne
    Stock stock via stockUpdate;
}
API function to post a single stock update
@POST("v1/stock/stockupdate")
ApiResponse postStockUpdate(StockUpdate stockUpdate) {
    ...
}
API function to post multiple stock updates
@POST("v1/stock/stockupdates")
ApiResponse postStockUpdates(StockUpdate[] stockUpdates) {
    ...
}
CURL example for posting a single stock update
curl -u 'user:pwd' \
-H "Content-Type: application/json" \
-X POST "https://dev.mezzanineware.com/rest/mezzanine-tut-lesson-25/v1/stock/stockupdate" \
-d '{  
  "_id":"fb94f312-2f99-4d40-889a-414d4b09f1ac",
  "level":200,
  "price":100,
  "stocktakeDate":1522326277000,
  "shop":"4575470d-ee0a-474d-bd5f-1c370c6fc817",
  "stock":"80dc5655-9600-440b-86c0-614ccaef11fe"
}'
CURL example for posting multiple stock updates
curl -u 'user:pwd' \
-H "Content-Type: application/json" \
-X POST "https://dev.mezzanineware.com/rest/mezzanine-tut-lesson-25/v1/stock/stockupdates" \
-d '[  
  {  
    "_id":"b54fb253-7f61-4a5a-9ae8-fcf42c495892",
    "level":200,
    "price":100,
    "stocktakeDate":1522326277000,
    "shop":"4575470d-ee0a-474d-bd5f-1c370c6fc817",
    "stock":"80dc5655-9600-440b-86c0-614ccaef11fe"
  },
  {  
    "_id":"ae9b5e85-9c7f-4062-9cbd-84060dc2267d",
    "level":500,
    "price":160,
    "stocktakeDate":1522326277000,
    "shop":"4575470d-ee0a-474d-bd5f-1c370c6fc817",
    "stock":"d5f7cb6d-69ef-4e01-953d-151d89792155"
  }
]'




Example 2

This example highlights the following:

  • Using the @POST annotation
  • Posting of a non-persistent object.
  • Posting a blob value.
  • Making use of a path parameter.


Model body parameter object
object ApiFarmerDocumentation {
    uuid governmentAssistanceCertificateId;
    blob governmentAssistanceCertificate;
}
API function to post farmer documentation
@POST("v1/farmer/mobileNumber/{mobileNumber}/profile/documentation")
ApiResponse postFarmerDocumentation(ApiFarmerDocumentation farmerDocumentation, string mobileNumber) {
    ...
}
CURL example
curl -u 'user:pass' \
-H "Content-Type: application/json" \
-X POST "https://dev.mezzanineware.com/rest/v1/farmer/mobileNumber/27761231234/profile/documentation" \
-d '{  
  "farmerMobileNumber":"27763303624",
  "governmentAssistanceCertificateId":"7c3c66dd-992d-489a-a4e0-99da1f225422",
  "governmentAssistanceCertificate":"WW91IGFyZSBhcHByb3ZlZCBmb3IgZ292ZXJubWVudCBhc3Npc3RhbmNlLg==",
  "governmentAssistanceCertificate_fname__":"FarmerGovernmentAssistance.txt",
  "governmentAssistanceCertificate_size__":43,
  "governmentAssistanceCertificate_mtype__":"text/plain"
}'




Example 3

This example highlights the following:

  • Using the @GET annotation.
  • Using json as an API return type.


API GET function returning constructed json
@GET("v1/admin/count")
json countRecords() {
    json result = "{}";
    result.jsonPut("workouts", countWorkouts());
    result.jsonPut("workoutEntries", countWorkoutEntries());
    result.jsonPut("workoutExerciseGroups", countWorkoutExerciseGroups());
    result.jsonPut("workoutDataUpload", countUploadedWorkoutData());
    return result;
}
CURL example
curl -v \
-u "user:pass" \
-X GET https://dev.mezzanineware.com/rest/mezzanine-extended-inbound-api-test/v1/admin/count
Response example
{
    "workoutDataUpload": 1,
    "workoutEntries": 2817,
    "workoutExerciseGroups": 719,
    "workouts": 191
}




Example 4

This example highlights the following:

  • Using the @GET annotation.
  • Using the @ResponsExclude annotation.
  • Using the @ResponseExpand annotation.


Model objects and relationships
@NotTracked
persistent object Workout {
	.
	.
	.
    json entity;
}
 
persistent object WorkoutEntry {
	.
	.
	.

    @ManyToOne
    Workout workout via workoutEntries;

    @ManyToOne
    WorkoutExerciseGroup workoutExerciseGroup via workoutEntries;
}
 
persistent object WorkoutExerciseGroup {
	.
	.
	.
    @ManyToOne
    Workout workout via workoutExerciseGroups;
}
API GET function with custom object return type
@ResponseExclude("_id")
@ResponseExclude("entity")
@ResponseExpand("workoutEntries")
@ResponseExclude("workoutEntries._id")
@ResponseExpand("workoutExerciseGroups")
@ResponseExclude("workoutExerciseGroups._id")
@ResponseExpand("workoutExerciseGroups.workoutEntries")
@ResponseExclude("workoutExerciseGroups.workoutEntries._id")
@GET("v1/workout/latest")
Workout getLatestWorkoutDetails() {
    return getLatestWorkoutSummary();
}
CURL example
 curl -v \
-u "user:pass" \
-X GET https://dev.mezzanineware.com/rest/mezzanine-extended-inbound-api-test/v1/workout/latest




Example 5

This example highlights the following:

  • Using the @DELETE annotation.
  • Using a query parameter.
  • Using a date path parameter.
  • void return type for an API function.


@DELETE("v1/workout/date/{workoutDate}")
void deleteWorkoutByDate(bool purge, date workoutDate) {
    Workout workout = getWorkoutForDate(workoutDate);
    if(workout != null) {
        if(purge == true) {
            Workout:delete(workout);
        }
        else {
            workout.deleted = true;
        }
    }
}
curl -v \
-u "user:pass" \
-X DELETE https://dev.mezzanineware.com/rest/mezzanine-extended-inbound-api-test/v1/workout/date/1507464000000?purge=true




Example 6

This example highlights the following:

  • Using the @POST annotation
  • Native json return type
  • Setting the API response status code using api:setStatusCode 


// Create a json response and set the status code
json createResponse(int code, string message) {
    api:setStatusCode(code);
    json responseBody = "{}";
    responseBody.jsonPut("code", code);
    responseBody.jsonPut("message", message);
    return responseBody;
}

@POST("v1/support/ticket")
json postSupportTicket(SupportTicket supportTicket) {

    // Check the database for this ticket
    SupportTicket existingTicket = SupportTicket:read(supportTicket._id);

    // Validate duplicate id
    if(existingTicket != null) {
	return createResponse(400, "A support ticket with the specified id is already present in the system");
    }

    // Validate the request content
    if(supportTicket.text == null || String:length(supportTicket.text) == 0) {
	return createResponse(400, "A text value describing the support request has to be specified");
    }

    // Populate all fields for the support ticket
    supportTicket.receivedTime = Mez:now();
    supportTicket.spam = false;
    supportTicket.resolved = false;
    supportTicket.deleted = false;
    supportTicket.save();

    // Return a success response
    return createResponse(200, "The support ticket was successfully posted");
}
Custom response code request example 1
curl -v -u 'user:pwd' -H "Content-Type: application/json" -X POST "https://dev.mezzanineware.com/rest/mezzanine-status-code-test/v1/support/ticket" -d '{  
  "_id":"0e83d835-963e-4c9c-8340-75269d7c6c57",
  "text":"",
  "senderNumber":"27761231234"
}'
< HTTP/1.1 400 Bad Request
< Date: Fri, 30 Oct 2020 11:48:10 GMT
< Content-Type: application/json
< Content-Length: 88
< Connection: keep-alive
< Server: Helium 1.19.1-SNAPSHOT
< X-Powered-By: Mezzanine
< 
{ [88 bytes data]
100   185  100    88  100    97    437    482 --:--:-- --:--:-- --:--:--   920
* Connection #0 to host jm.stb.mezzanineware.com left intact
* Closing connection 0
{
    "code": 400,
    "message": "A text value describing the support request has to be specified"
}
Custom response code request example 2
curl -v -u 'usr:pwd' -H "Content-Type: application/json" -X POST "https://dev.mezzanineware.com/rest/mezzanine-status-code-test/v1/support/ticket" -d '{  
  "_id":"0e83d835-963e-4c9c-8340-75269d7c6c57",
  "text":"Please help..",
  "senderNumber":"27761231234"
}'
< HTTP/1.1 200 OK
< Date: Fri, 30 Oct 2020 11:48:17 GMT
< Content-Type: application/json
< Content-Length: 67
< Connection: keep-alive
< Server: Helium 1.19.1-SNAPSHOT
< X-Powered-By: Mezzanine
< 
{ [67 bytes data]
100   166  100    67  100    99   1063   1571 --:--:-- --:--:-- --:--:--  2634
* Connection #0 to host jm.stb.mezzanineware.com left intact
* Closing connection 0
{
    "code": 200,
    "message": "The support ticket was successfully posted"
}


Built-in APIs

In addition to the APIs developed as part of a DSL application, some internal APIs are also available through the same mechanism described in the tutorial. These include APIs to query app health and return the current list of scheduled functions for an app. Below are examples demonstrating these APIs.



Example: Scheduled Functions

As with all app inbound APIs, requests can be made using the friendly API name or the app ID:

Request using friendly API name
curl -v -u 'usr:pwd' \
-H "Content-Type: application/json" \
-X GET "https://dev.mezzanineware.com/rest/mezzanine-meta-test/built-in/meta/scheduled-function"
Request using app ID
curl -v -u 'usr:pwd' \
-H "Content-Type: application/json" \
-X GET "https://dev.mezzanineware.com/rest/0f9172b2-cd0e-4e5b-9b81-7e61fb601edb/built-in/meta/scheduled-function"
[
    {
        "schedule": "*/5 * * * *",
        "unit": "LogUnit",
        "function": "logSomething"
    },
    {
        "schedule": "*/2 * * * *",
        "unit": "LogUnit",
        "function": "logSomethingElse"
    }
]


Example: App Health

The example request shown below makes use of the app ID, but once again, either the app ID or the friendly API name may be used.

curl -v -u 'usr:pwd' \
-H "Content-Type: application/json" \
-X GET "https://dev.mezzanineware.com/rest/b2482dd5-866e-410e-9a52-5d9217987521/built-in/meta/health"

Apps that have an active current release and that can be reached through inbound API will result in a 200 response code with a body as shown below:

Example response for a healthy app
{
    "appId": "b2482dd5-866e-410e-9a52-5d9217987521",
    "appName": "Test App 1 - Has release and compiles",
    "jiraCode": "jira-code",
    "appDataSource": "default",
    "appDeployedTstamp": 1692968922892,
    "appDeployedTstampDesc": "2023-08-25 01:08:42",
    "appReleaseId": "d216247e-82e2-4f62-bf46-8df0b662f79f",
    "appReleaseName": "My Test Release",
    "appReleaseUser": "admin",
    "appReleaseTstamp": 1692968920937,
    "appReleaseTstampDesc": "2023-08-25 01:08:40",
    "compilable": true,
    "error": null,
    "errorDetail": [],
    "disabled": false,
    "disabledMessage": "",
    "disabledReason": "None",
    "locked": false,
    "lockedMessage": ""
}

Apps that do not have an active current release will result in a 500 response as shown below:

Example response for an app with no release
500 The ID for the application instance is not associated with a compilable application instance

Apps that have an active release that causes compile or schema migration issues will result in a 500 response code and respective bodies as shown below:

Example response for app that does not compile due to schema upgrade / SQL issues
{
    "appId": "192cb078-acdf-6219-11b2-6582636ab4bb",
    "appName": "Test App 3 - Has release but does not compile-SQL",
    "jiraCode": null,
    "appDataSource": "default",
    "appDeployedTstamp": 1695201850258,
    "appDeployedTstampDesc": "2023-09-20 09:24:10",
    "appReleaseId": "09f4b36f-75cf-4c10-a4e2-35a10fd65b65",
    "appReleaseName": "My Test Release - Uncompilable",
    "appReleaseUser": "admin",
    "appReleaseTstamp": 1695201837993,
    "appReleaseTstampDesc": "2023-09-20 09:23:57",
    "compilable": false,
    "error": "com.mezzanine.persistence.AppPersistenceException: SQL error in /sql-scripts/create_default_stock.sql: ERROR: relation \"somethingthatdoesntexist\" does not exist\n  Position: 15",
    "errorDetail": [],
    "disabled": false,
    "disabledMessage": "",
    "disabledReason": "None",
    "locked": false,
    "lockedMessage": ""
}
Example response for app that does not compile due to general compile issues
{
    "appId": "ae19aeca-65ab-6d5b-ed09-8a748292b6b6",
    "appName": "Test App 4 - Has release but does not compile-DSL",
    "jiraCode": null,
    "appDataSource": "default",
    "appDeployedTstamp": 1695209063722,
    "appDeployedTstampDesc": "2023-09-20 11:24:23",
    "appReleaseId": "ff274989-061a-4673-b5a5-631af35fd63e",
    "appReleaseName": "My Test Release - Uncompilable 2",
    "appReleaseUser": "admin",
    "appReleaseTstamp": 1695204028206,
    "appReleaseTstampDesc": "2023-09-20 10:00:28",
    "compilable": false,
    "error": "com.mezzanine.program.web.compiler.CompilerException",
    "errorDetail": [
        "FakeInboundMessages:5 - The variable is defined to be a collection of type InvalidType, but this custom type is not defined in the application",
        "FakeInboundMessages:18 - The variable is defined to be a collection of type InvalidType, but this custom type is not defined in the application"
    ],
    "disabled": false,
    "disabledMessage": "",
    "disabledReason": "None",
    "locked": false,
    "lockedMessage": ""
}


Additional Mentions and References

For more detail on the inbound API feature as described on this page, please see the related documentation below, specifically the Helium tutorial lesson which describes detailed examples and provides complete accompanying source code.