---
sidebar_label: Action specifications
toc_max_heading_level: 3
doc_id: ee2c3da0-4b5a-42ba-8f39-8013afc858f8
description: >-
  Guide for designing and implementing action specifications for services and
  links in nullplatform.
keywords:
  - action specifications
  - services
  - links
  - JSON schema
  - API
  - parallel execution
---

import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';

# Action specifications

:::note Before you start

If you haven't done it yet, read about [services' main concepts](../getting-started.md#specifications).

Also keep these handy references:

* **Service Action Specification API**
  * [Our API reference](/docs/services-service-action-api-index)
* **Link Action Specification API**
  [ [POST](/docs/api/link-specification-action-create) | [PATCH](/docs/api/link-specification-action-update) | [GET](/docs/api/link-specification-action-read) | [LIST](/docs/api/link-specification-action-list) | [DELETE](/docs/api/link-specification-action-delete) ]
* **Schemas**
    * [JSON Schema (external link)](https://json-schema.org/learn/getting-started-step-by-step) | [Additional keywords supported by nullplatform](/docs/json-ui-schema/json-schema)
    * [UI Schema (external link)](https://jsonforms.io/docs/uischema/) | [How UI schema is integrated in nullplatform](/docs/json-ui-schema/overview)
      :::


:::note This guide applies to **services** and **links**
Creating actions for services and links is exactly the same process. Unless stated otherwise, every time we refer to
services or service specifications you can automatically assume the same applies to links and link specifications.
:::

## Design your action specifications

Now that you have your service specifications, you bring them to life by designing create, update, and delete
actions.

Here are some design questions to answer:

| Question                                                       | Guidance                                                                                                                                                                                                              |
| -------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Which input parameters do I need to ask to create the service? | This is a central design question that will determine the content of the JSON schema under the `attributes` field.                                                                                                    |
| Which parameters will I allow to be edited?                    | Be mindful of the underlying capabilities of your cloud-based services. Also, be careful not to allow changing parameters that can’t be changed in place and would trigger resource re-creation by your IaC tool. |
| Which custom actions do I want to provide?                     | These will be actions that serve a specific operational purpose such as "Manually push an event to the queue".                                                                                                        |
| Do I need to store results for my action?                      | Results play a key role because they connect actions to the service specifications. Also, custom action results are displayed to the end user for easier interpretation.                                               |

## Design all required actions

Services usually require actions to create, update, or delete the service. On top of this, you can also add your own
custom actions. This means that you'll likely have to design and create several actions for a service.

Your actions need to declare one of these action types: `create`, `update`, or `delete`, unless you are
creating a custom action, which requires an action of type `custom`.

## Manually defining actions vs. autogenerated ones

In most cases, you don’t need to manually define `create`, `update`, or `delete` actions.

New service (and link) specifications autogenerate them by default (`use_default_actions` defaults to `true`): the three actions are built from the spec's attributes schema and stay in sync with it, requiring less maintenance.

Standard actions are autogenerated and cannot be edited directly. If you need to modify them, you must update the **service specification schema** instead.

To define your own `create`, `update`, or `delete` action specifications, the specification needs `"use_default_actions": false`. Set it explicitly when you create the spec, since new specifications default to `true` and the autogenerated trio can't be overridden. Custom actions are always allowed, with or without autogenerated actions.

> 
> To learn how to enable autogenerated actions, see:
>- [Service specifications](/docs/services/craft-a-service/service-specs)
>- [Link specifications](/docs/services/craft-a-service/link-specs)
>

Still, there are situations where defining action specifications manually is the right choice, especially when creating custom actions or needing to update standard actions. This page covers how to do that.



## Parameters and results

If you're defining action specifications manually, for each action you'll have to define these fields:

- **`parameters`**: the input requirements for the action, expressed as a JSON schema.
- **`results`**: the expected output of the action, also expressed as a JSON schema.

  :::note UI Schema
  See the [UI schema reference section](/docs/json-ui-schema/overview) for more information on how to manipulate
  the fields in the UI.
  :::


## Action specifications

To create, update, or destroy a service, you typically run an action (this is optional but common).

If you're interacting with the service through the UI, nullplatform looks up the corresponding action
specifications and creates a new action instance. If you're doing a custom integration, you have to call these
actions explicitly.

:::note Action types
Use the `type` field on the action specification to distinguish between `create`, `update`, `delete`, and `custom`
actions.
:::

In the following subsections we provide examples for each of these action types and review our [API reference](/docs/services-action-api-index) for more info.

### Create

<Tabs
defaultValue="create-service-action-cli"
values={[
{ label: 'CLI', value: 'create-service-action-cli' },
{ label: 'cURL', value: 'create-service-action-curl' },
]}>
<TabItem value="create-service-action-cli">

    ```bash
    np service-specification action-specification create \
     --serviceSpecificationId $service_spec_id \
     --body '{
        "name": "Create my service action spec",
        "type": "create",
        "parameters": {
          "schema": {
            "type": "object",
            "properties": {}
          },
          "values": {}
        },
        "results": {
          "schema": {
            "type": "object",
            "properties": {}
          },
          "values": {}
        }
      }'
    ```

  </TabItem>
  <TabItem value="create-service-action-curl">

    ```bash
    curl -X POST "https://api.nullplatform.com/service_specification/$service_spec_id/action_specification" \
      -H "Content-Type: application/json" \
      -d '{
        "name": "Create my service action spec",
        "type": "create",
        "parameters": {
          "schema": {
            "type": "object",
            "properties": {}
          },
          "values": {}
        },
        "results": {
          "schema": {
            "type": "object",
            "properties": {}
          },
          "values": {}
        }
      }'
    ```

  </TabItem>
</Tabs>

### Update

<Tabs
defaultValue="create-service-update-action-cli"
values={[
{ label: 'CLI', value: 'create-service-update-action-cli' },
{ label: 'cURL', value: 'create-service-update-action-curl' },
]}>
<TabItem value="create-service-update-action-cli">

    ```bash
    np service-specification action-specification create \
     --serviceSpecificationId $service_spec_id \
     --body '{
        "name": "Create my service update action spec",
        "type": "update",
        "parameters": {
          "schema": {
            "type": "object",
            "properties": {}
          },
          "values": {}
        },
        "results": {
          "schema": {
            "type": "object",
            "properties": {}
          },
          "values": {}
        }
      }'
    ```

  </TabItem>
  <TabItem value="create-service-update-action-curl">

    ```bash
    curl -X POST "https://api.nullplatform.com/service_specification/$service_spec_id/action_specification" \
      -H "Content-Type: application/json" \
      -d '{
        "name": "Create my service update action spec",
        "type": "update",
        "parameters": {
          "schema": {
            "type": "object",
            "properties": {}
          },
          "values": {}
        },
        "results": {
          "schema": {
            "type": "object",
            "properties": {}
          },
          "values": {}
        }
      }'
    ```

  </TabItem>
</Tabs>

### Delete

<Tabs
defaultValue="create-service-delete-action-cli"
values={[
{ label: 'CLI', value: 'create-service-delete-action-cli' },
{ label: 'cURL', value: 'create-service-delete-action-curl' },
]}>
<TabItem value="create-service-delete-action-cli">

    ```bash
    np service-specification action-specification create \
     --serviceSpecificationId $service_spec_id \
     --body '{
        "name": "Create my service delete action spec",
        "type": "delete",
        "parameters": {
          "schema": {
            "type": "object",
            "properties": {}
          },
          "values": {}
        },
        "results": {
          "schema": {
            "type": "object",
            "properties": {}
          },
          "values": {}
        }
      }'
    ```

  </TabItem>
  <TabItem value="create-service-delete-action-curl">

    ```bash
    curl -X POST "https://api.nullplatform.com/service_specification/$service_spec_id/action_specification" \
      -H "Content-Type: application/json" \
      -d '{
        "name": "Create my service delete action spec",
        "type": "delete",
        "parameters": {
          "schema": {
            "type": "object",
            "properties": {}
          },
          "values": {}
        },
        "results": {
          "schema": {
            "type": "object",
            "properties": {}
          },
          "values": {}
        }
      }'
    ```

  </TabItem>
</Tabs>

### Custom

<Tabs
defaultValue="create-service-custom-action-cli"
values={[
{ label: 'CLI', value: 'create-service-custom-action-cli' },
{ label: 'cURL', value: 'create-service-custom-action-curl' },
]}>
<TabItem value="create-service-custom-action-cli">

    ```bash
    np service-specification action-specification create \
     --serviceSpecificationId $service_spec_id \
     --body '{
        "name": "Create my service custom action spec",
        "type": "custom",
        "parameters": {
          "schema": {
            "type": "object",
            "properties": {
              "message": {
                "type": "string"
              },
              "delay_in_seconds": {
                "type": "integer",
                "default": 0
              }
            }
          },
          "values": {}
        },
        "results": {
          "schema": {
            "type": "object",
            "properties": {
              "message_id": {
                "type": "string"
              }
            }
          },
          "values": {}
        }
      }'
    ```

  </TabItem>
  <TabItem value="create-service-custom-action-curl">

    ```bash
    curl -X POST "https://api.nullplatform.com/service_specification/$service_spec_id/action_specification" \
      -H "Content-Type: application/json" \
      -d '{
        "name": "Create my service custom action spec",
        "type": "custom",
        "parameters": {
          "schema": {
            "type": "object",
            "properties": {
              "message": {
                "type": "string"
              },
              "delay_in_seconds": {
                "type": "integer",
                "default": 0
              }
            }
          },
          "values": {}
        },
        "results": {
          "schema": {
            "type": "object",
            "properties": {
              "message_id": {
                "type": "string"
              }
            }
          },
          "values": {}
        }
      }'
    ```

  </TabItem>
</Tabs>

#### Some attributes to consider

Let’s look more closely at some of the fields we specify on the POST and PATCH:

- **`results`** has two fields:
    - `schema`: a JSON schema for the results.
    - `results`: the actual results for the action.

- **`retryable`**: set to `true` if you want the action to be triggered again after failures (see
  [Retryable actions](#retryable-actions) section).

- **`external`**: configure [external field resolution](/docs/services/craft-a-service/external-context) to fetch
  dynamic data from your infrastructure at form-render time. This lets `additionalKeywords` expressions reference
  real-time values like database users or cloud resources via `.external`.


## Run custom service actions in parallel

By default, service actions in nullplatform run sequentially. This ensures safety but can limit throughput when
actions are independent.
With the `parallelize` flag, you can run certain **custom service actions in parallel**, improving performance for
workloads that benefit from concurrency.

**When to use parallel execution**

Parallel execution is useful when your custom action does not depend on the order of operations or shared state.
Common scenarios include:

- **High-throughput messaging:** Sending multiple SQS messages at the same time.
- **Batch operations:** Writing records concurrently to DynamoDB or another data store.
- **Fan-out integrations:** Triggering multiple HTTP calls, webhooks, or events simultaneously.

> ℹ️ **Best practice:** Only parallelize actions that are **idempotent** or do not affect each other. Actions that
mutate shared resources or rely on ordering should remain sequential.

### How to enable parallel execution

To enable parallel execution, set `"parallelize": true` in the action specification of the custom action.

```json
{
  "actions": {
    "send-message": { "parallelize": true },
    "poll-for-messages": { "parallelize": false }
  }
}
```
- `parallelize: true` → action can be run in parallel with other actions.
- `parallelize: false` → action must run sequentially.

This allows you to fine-tune which actions are safe for concurrency and which must remain ordered.

> 📖 See our [API reference](/docs/api/service-specification-actions-create) for more info. 


## Running actions more than once

### Only one action at a time

To prevent failure scenarios, we reject running a create, update, or delete action while there's another action of the
same type already running. Note that this rule **does not apply for custom actions**.

### Retryable actions

For services where it’s safe to retry an action after failure, you can use the `retryable` field set to `true` in the
action specification. This tells nullplatform that the action can be triggered again under certain conditions.

Here’s what you need to know:

| Action type | Considerations                                                                                                                        |
| ----------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| Create      | The service instance must have previously failed to create. You can’t retry a create action on an already successful or existing one. |
| Update      | The service must have been successfully created before the failed update.                                                             |
| Delete      | The delete action is retryable only if **no prior delete action has succeeded**.                                                      |
| Custom      | The service must have been successfully created before retrying.                                                                      |

:::note
**Autogenerated standard actions** follow these retryability rules by default:
- `create` actions **are not** retryable
- `update` and `delete` actions **are** retryable
:::

#### How to retry actions

You can retry failed service actions from the UI or via API:

**From the UI:**

1. Go to **Development > Services**.
2. Click **Manage > History** on the service that failed.
3. Click **Retry** to re-run the action with the same inputs.

**From the API or CLI:**

Send a POST request to the [Create a service action](/docs/api/service-action-create) endpoint with the same parameters.


## Important rules about actions

### What happens when an action fails?

When a `create`, `update`, or `delete` action fails, the service instance gets modified and is placed in a "failed"
state which requires manual intervention to assess if the service needs repairing or can be placed again in an
active state. Therefore, when these actions fail, users are prevented from running any of these actions again.

> **Note**: See our [Troubleshooting](/docs/services/troubleshooting) page for more info.

### You can't create standard actions if action specifications already exist

Autogenerated standard actions help simplify the service creation workflow, allowing you to get started with a basic setup quickly. 

However, if you've already created and linked action specifications to a service, you won't be able to enable standard actions by setting `use_default_actions` to `true`.

This ensures there's no conflict between manually defined actions and autogenerated ones.

### How to override standard actions

Standard actions cannot be modified directly in the service specification, but you can override them by creating action specifications manually.

1. Set `use_default_actions` to `false` in your service specification.
2. Update action specifications for `create`, `update`, and `delete` actions.

This setup tells the platform to use your manually-defined actions instead of the autogenerated ones.

### How deleting services works

To avoid orphaned infrastructure and unnecessary costs, the following rules apply to deleting a service:

- If a service has a `delete` action, **that action must be executed** before deletion.
- Alternatively, deletion can be **forced** with a specific parameter (`force=true`).

Trying to delete a service without executing the `delete` action results in an error unless the override is used.

Here's an example:

<Tabs
defaultValue="service-delete-cli"
values={[
{ label: 'CLI', value: 'service-delete-cli' },
{ label: 'cURL', value: 'service-delete-curl' },
]}>
  <TabItem value="service-delete-cli">
      ```bash
      np service delete --id <SERVICE_ID> --force true
      ```
  </TabItem>
  <TabItem value="service-delete-curl">
        ```bash
        curl -X DELETE 'https://api.nullplatform.com/service/<SERVICE_ID>?force=true' \
        -H 'Authorization: Bearer <token>'
        ```
  </TabItem>
</Tabs>

For more info, see our [API reference docs](/docs/api/service-delete). 
