You are reading the documentation for an outdated Corteza release. 2023.9 is the latest stable Corteza release.

Examples

This guide is tailored to readers who prefer to learn by example. It covers all sections of the product and provides a wide variety of examples to help you get started using the API, configuring data models, managing permissions and much more.

System API

Any operation doable via the front-end application is doable via the API; either a single endpoint or a combination of.

The system API is responsible for core system resources such as users and roles. It is also responsible for core operations such as authentication.

Whenever an operation affects the system and is not specific to any of our applications, you will most likely need to use the system API

We omit most of the data returned by these endpoints. We replace the important data with variables, such as $JWT so that we can copy-paste these examples over any environment.

  • URL path: /system,

  • alias: $SystemAPI,

Authenticating users

To authenticate the user with their login credentials (email - $USER_EMAIL; password - $USER_PASSWORD), we use the POST $SystemAPI/auth/internal/login endpoint.

The response is the following JSON object:

{
  "jwt": "$JWT",
  "user": {
    "userID": "$USER_ID",
    "name": "$USER_NAME",
    "email": "$USER_EMAIL",
    "username": "$USER_USERNAME",
    "handle": "$USER_HANDLE"
  }
}

The received $JWT token can be used for authenticating API requests.

The $JWT token is bearer, so you must prefix it with Bearer, so for example Bearer $JWT.

Example request

curl "$SystemAPI/auth/internal/login" \
 --data-binary "{\"email\":\"$USER_EMAIL\",\"password\":\"$USER_PASSWORD\"}";

Example response

{
  "response": {
    "jwt": "$JWT",
    "user": {
      "userID": "$USER_ID",
      "name": "$USER_NAME",
      "email": "$USER_EMAIL",
      "username": "$USER_USERNAME",
      "handle": "$USER_HANDLE"
    }
  }
}

Compose API

Any operation doable via the front-end application is doable via the API; either a single endpoint or a combination of.

The compose API is responsible for Corteza Low Code related resources such as modules, records and charts.

Whenever an operation affects Compose, you will most likely need to use the compose API.

We omit most of the data returned by these endpoints. We replace the important data with variables, such as $RECORD_ID so that we can copy-paste these examples over any environment.

  • URL path: /compose,

  • alias: $ComposeAPI,

Sending emails

To send an email to a recipient or a set of, call the POST $ComposeAPI/notification/email/send endpoint.

Refer to the API reference to find all the available parameters.

Make sure that you’ve properly configured your environment with the SMTP credentials.

Example request

curl "$ComposeAPI/notification/email" \
  -H "Authorization: Bearer $JWT" \
  --data "{
    \"to\": [\"$USER_EMAIL\"],
    \"subject\": \"Test CURL email\",
    \"content\": { \"html\": \"<div>Test Content</div>\" }
  }"

Example response

{
  "response": true
}

Record listing

Record listing provides you with a list of records that conform to the given constraints. Use this when you wish to show some sort of list of available records, such as a record list.

To list records stored under a specific module, use the GET $ComposeAPI/namespace/$NAMESPACE_ID/module/$MODULE_ID/record endpoint.

Using filters

If you don’t need filtering simply omit this.

When filtering over records, use module field names as $PROPERTY_NAME in your expressions.

Filters allow you to define…​

Filter by data

Use the query=…​ query property to specify what items should be included in the response.

We provide a SQL-like query language in the form of $PROPERTY_NAME=$VALUE [$BITWISE_OPERATOR $PROPERTY_NAME=$VALUE […​]].

Sorting

Use the sort=…​ query property to specify how to the response items should be sorted.

We provide a SQL-like sorting language in the form of $PROPERTY_NAME $ORDER [, $PROPERTY_NAME $ORDER[, …​]].

Pagination

Use the page=…​ and perPage=…​ query properties to specify the pagination properties of the response.

Pagination is 1-based, meaning the first page index is 1.

If you wish to list all items, define perPage=0.

Example request

curl "$ComposeAPI/namespace/$NAMESPACE_ID/module/$MODULE_ID/record?filter=$FIELD1+LIKE+'%25$VALUE1%25'+AND+$FIELD2+LIKE+'%25$VALUE2%25'&sort=$SORT_FIELD+$SORT_DIR&page=$PAGE&perPage=$PER_PAGE" \
  -H "Authorization: Bearer $JWT";

Example response

{
  "response": {
    "filter": {
      "moduleID": "$MODULE_ID",
      "namespaceID": "$NAMESPACE_ID",
      "query": "$FIELD1 LIKE '%$VALUE1%' AND $FIELD2 LIKE '%$VALUE2%'",
      "sort": "$SORT_FIELD $SORT_DIR",
      "page": $PAGE,
      "perPage": $PER_PAGE,
      "count": 1(1)
    },
    "set": [{
      "recordID": "$RECORD_ID",
      "moduleID": "$MODULE_ID",
      "namespaceID": "$NAMESPACE_ID",
      "values": [
        { "name": "fieldName", "value": "fieldValue" }
        ...
      ]
    }]
  }
}
1 The total number of records that match the provided filter.

Record reading

Record reading provides you a single record that you wish to get the details for. Use this when you wish to show a specific record, for example when clicking on a specific record in a record list.

To get a specific record stored under a specific module, use the GET $ComposeAPI/namespace/$NAMESPACE_ID/module/$MODULE_ID/record/$RECORD_ID endpoint.

If you wish to read multiple records at the same time, instead of making N requests, simply use Record listing with the query of query=recordID=$RECORD_ID_1+OR+recordID=$RECORD_ID_2+OR+…​

Example request

curl "$ComposeAPI/namespace/$NAMESPACE_ID/module/$MODULE_ID/record/$RECORD_ID" \
  -H "Authorization: Bearer $JWT";

Example response

{
  "response": {
    "recordID": "$RECORD_ID",
    "moduleID": "$MODULE_ID",
    "namespaceID": "$NAMESPACE_ID",
    "values": [
      { "name": "fieldName", "value": "fieldValue" }
      ...
    ]
  }
}

Creating a record

To create a record for a specific module, use the POST $ComposeAPI/namespace/$NAMESPACE_ID/module/$MODULE_ID/record endpoint.

Example request

curl "$ComposeAPI/namespace/$NAMESPACE_ID/module/$MODULE_ID/record/" \
  -H "Authorization: Bearer $JWT" \
  -H 'Content-Type: application/json' \
  --data-binary "{
    \"values\": [
      { \"name\": \"$FIELD1\", \"value\": \"$VALUE1\" },
      { \"name\": \"$FIELD2\", \"value\": \"$VALUE2\" }
    ]
  }";

Example response

{
  "response": {
    "recordID": "$RECORD_ID",
    "moduleID": "$MODULE_ID",
    "namespaceID": "$NAMESPACE_ID",
    "values": [
      { "name": "$FIELD1", "value": "$$VALUE1" },
      { "name": "$FIELD2", "value": "$$VALUE2" },
    ]
  }
}

Updating a record

To update a specific record for a specific module, use the POST $ComposeAPI/namespace/$NAMESPACE_ID/module/$MODULE_ID/record/$RECORD_ID endpoint.

The new record values will be as provided in the update request. The values that are omitted in the update request will be removed from the given record.

Example request

curl "$ComposeAPI/namespace/$NAMESPACE_ID/module/$MODULE_ID/record/$RECORD_ID" \
  -H "Authorization: Bearer $JWT" \
  -H 'Content-Type: application/json' \
  --data-binary "{
    \"values\": [
      { \"name\": \"$FIELD1\", \"value\": \"$VALUE1\" },
      { \"name\": \"$FIELD2\", \"value\": \"$VALUE2\" }
    ]
  }";

Example response

{
  "response": {
    "recordID": "$RECORD_ID",
    "moduleID": "$MODULE_ID",
    "namespaceID": "$NAMESPACE_ID",
    "values": [
      { "name": "$FIELD1", "value": "$$VALUE1" },
      { "name": "$FIELD2", "value": "$$VALUE2" },
    ]
  }
}

Deleting a record

To delete a specific record for a specific module, use the DELETE $ComposeAPI/namespace/$NAMESPACE_ID/module/$MODULE_ID/record/$RECORD_ID endpoint.

Example request

curl -X DELETE "$ComposeAPI/namespace/$NAMESPACE_ID/module/$MODULE_ID/record/$RECORD_ID" \
  -H "Authorization: Bearer $JWT";

Example response

{
  "success": {
    "message":"OK"
  }
}

Download attachment

Attachments are protected resources and require you to generate a signature in order to access them.

To download the attachment you must perform the following:

Obtain a signed download URL

When fetching a specific attachment via the GET $ComposeAPI/namespace/$NAMESPACE_ID/attachment/record/$ATTACHMENT_ID or listing attachments via the GET $ComposeAPI/namespace/$NAMESPACE_ID/attachment/record you will receive the following JSON object:

{
  "attachmentID": "$ATTACHMENT_ID",
  "ownerID": "$USER_ID",
  "url": "$ATTACHMENT_ORIGINAL_URL",(1)
  "previewUrl": "$ATTACHMENT_PREVIEW_URL",(2)
  "name": "$FILENAME_ORIGINAL",
  "meta": {...},
  "namespaceID": "$NAMESPACE_ID"
}
1 url contains a signed URL path to the attachment.
2 previewUrl contains a signed URL path to the preview version of the attachment, when available.

Example request

curl "$ComposeAPI/namespace/$NAMESPACE_ID/attachment/record/$ATTACHMENT_ID" \
 -H "Authorization: Bearer $JWT";

Example response

{
  "response": {
    "attachmentID": "$ATTACHMENT_ID",
    "ownerID": "$USER_ID",
    "url": "$ATTACHMENT_ORIGINAL_URL",
    "previewUrl": "$ATTACHMENT_PREVIEW_URL",
    "name": "$FILENAME_ORIGINAL",
    "meta": {...},
    "namespaceID": "$NAMESPACE_ID"
  }
}

Download the attachment

Use the attachments response.url property to get the path of the downloadable attachment. For example, /namespace/$NAMESPACE_ID/attachment/record/$ATTACHMENT_ID/original/$ATTACHMENT_NAME.$ATTACHMENT_EXT?sign=$ATTACHMENT_SIGNATURE&userID=$SIGNATURE_OWNER.

Get the full path by joining the compose API base URL and the provided attachment path. For example: $composeAPI/$ATTACHMENT_ORIGINAL_URL.

You can then use any approach you wish to either display or download the attachment, such as using the <a href="…​">…​</a> element, using Axios or any other client.

Server scripts

Automation scripts allow you to implement custom business logic that is required for your needs. Server scripts are executed in the Corteza Corredor. Refer to integrator guide/extensions for more details.

Since they are executed on the server, you can’t manipulate the user interface. Use client scripts if this is needed.

Send an email to the contact

The example script sends an email to the contact it was invoked for.

Make sure that your SMTP configuration is working.

server-scripts/Contact/SendMail.js
export default {
  label: "Script label",
  description: 'Script description',

  * triggers ({ on }) {
    // This script myst be invoked manually (explicitly)
    yield on('manual')
      // for a record
      .for('compose:record')
      // if the record belongs to the Quote module
      .where('module', 'Contact')
      // and the module belongs to the crm namespace -- this is the slug
      .where('namespace', 'crm')
      // visible in the compose application
      .uiProp('app', 'compose')
  },

  // Refer to the integrator guide for details on these two parameters
  async exec ({ $record }, { Compose }) {
    let emailContent
    let emailSubject

    // Determine the email content and subject.
    // You could also do other bits inhere

    if (!$record.values.Email) {
      // This will stop the script's execution
      return false
    }

    await Compose.sendMail(
      $record.values.Email,
      emailSubject,
      { html: emailContent }
    )
  }
}

Notify owner about the update

The example fetches the lead owner and sends them an email.

Make sure that your SMTP configuration is working.

server-scripts/Lead/NotifyChange.js
export default {
  label: "Script label",
  description: 'Script description',

  * triggers ({ after }) {
    // This script myst be invoked after the record is updated (implicitly)
    yield after('update')
      // for a record
      .for('compose:record')
      // if the record belongs to the Lead module
      .where('module', 'Lead')
      // and the module belongs to the crm namespace -- this is the slug
      .where('namespace', 'crm')
  },

  // Refer to the integrator guide for details on these two parameters
  async exec ({ $record }, { Compose, System }) {
    let emailContent
    let emailSubject

    if (!$record.ownedBy) {
      // This will stop the script's execution
      return false
    }

    // Lets get the owner
    const owner = await System.findUserByID($record.ownedBy)

    // Determine the email content and subject.
    // You could also do other bits inhere

    await Compose.sendMail(
      owner.email,
      emailSubject,
      { html: emailContent }
    )
  }
}

Calculate the lead cost

The example calculates the lead cost when it is created or updated.

server-scripts/Lead/UpdateCost.js
export default {
  label: "Script label",
  description: 'Script description',

  * triggers ({ before }) {
    // This script myst be invoked before the record is created or updated (implicitly)
    yield before('create', 'update')
      // for a record
      .for('compose:record')
      // if the record belongs to the Lead module
      .where('module', 'Lead')
      // and the module belongs to the crm namespace -- this is the slug
      .where('namespace', 'crm')
  },

  // Refer to the integrator guide for details on these two parameters
  async exec ({ $record }, { Compose }) {
    if (!$record.values.LeadSource) {
      // This will use the original record, since no changes are required.
      // You could also use return false to stop the execution
      return $record
    }

    switch ($record.values.LeadSource) {
      case 'source-a':
        $record.values.LeadCost = 10
        break

      case 'source-b':
        $record.values.LeadCost = 20
        break

      default:
        $record.values.LeadCost = 30
        break
    }

    // Returning $record in a before script will automatically update the record.
    // IMPORTANT: This is not the same for after scripts -- they need to be explicitly updated.
    return $record
  }
}

Client scripts

Automation scripts allow you to implement custom business logic that is required for your needs. Client scripts are executed in the client’s browser. Refer to integrator guide/extensions for more details.

Prompt notification

This example prompts the user to enter a value and then displays it as a notification.

client-scripts/compose/crm/Contact/CollectValue.js
export default {
  label: "Script label",
  description: 'Script description',

  * triggers ({ on }) {
    // This script myst be invoked manually (explicitly)
    yield on('manual')
      // for a record
      .for('compose:record')
      // if the record belongs to the Quote module
      .where('module', 'Contact')
      // and the module belongs to the crm namespace -- this is the slug
      .where('namespace', 'crm')
      // visible in the compose application
      .uiProp('app', 'compose')
  },

  // Refer to the integrator guide for details on these two parameters
  async exec ({ $record }, { Compose, ComposeUI }) {

    const value = window.prompt('Please insert a value')
    if (!value) {
      ComposeUI.warning('No value provided')
      return false
    }

    // Do something with the inserted value
    // ...

    ComposeUI.success(`Hi! You've entered ${value}!`)
  }
}

Prefill values

This example prefills some record values in case they are not provided.

This can also be done with the module field default value setting.

client-scripts/compose/crm/Contact/Prefill.js
export default {
  label: "Script label",
  description: 'Script description',

  * triggers ({ before }) {
    // This script myst be invoked manually (explicitly)
    yield before('formSubmit')
      // for a record
      .for('compose:record')
      // if the record belongs to the Request module
      .where('module', 'Request')
      // and the module belongs to the crm namespace -- this is the slug
      .where('namespace', 'crm')
  },

  // Refer to the integrator guide for details on these two parameters
  async exec ({ $record, $module }, { Compose }) {
    // Lets get the defaults from a Default module.
    // This allows some more flexibility
    const defaults = await Compose.findFirstRecord('Defaults')

    for (const k in $record.values) {
      if (!$record.values[k]) {
        $record.values[k] = defaults.values[k]
      }
    }

    // IMPORTANT: client-scripts work with references, so you don't need to
    // explicitly return the $record -- this is already applied when we
    // assigned a new value above.
    return $record
  }
}