---
sidebar_label: Archive services and links
toc_max_heading_level: 3
doc_id: 78ded825-29fa-4772-af5c-3f6f43cec3c2
description: >-
  Archive services and links instead of deleting them. Archived instances are
  hidden from lists and keep their configuration, so you can restore them at
  any time.
keywords:
  - services
  - links
  - archive
  - restore
  - soft delete
  - lifecycle
---

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

# Archive services and links <span className="heading-release-pill">New</span>

:::info 🚀 Early release

Archiving is in early release and may not be available in your organization yet. If you'd like to try it or want to know more, [reach out to us](https://docs.nullplatform.com/docs/support) and we'll get you set up.

:::

Archiving is a soft delete for services and links. An archived instance stops showing up in your service lists and its link parameters are removed, but the instance and all its attributes stay in nullplatform, so you can restore it whenever you need it.

Use archive when you're decommissioning gradually, pausing a service you may need again, or cleaning up lists without losing configuration. Hard delete is still available, with its usual guards, for when you're sure you won't need the service again.

## How archiving works

When you archive a service or link:

- The instance moves to the `archived` status, and `archived_at` records when it happened.
- If the specification has an `archive` action, nullplatform runs it so your infrastructure can react (for example, quarantining a queue or pausing a database).
- For links, nullplatform removes the link's parameters from the application. Restoring the link recreates them.
- The instance and its attributes stay stored and restorable at any time.

Restoring works the same way in reverse: the instance moves to `updating` and then back to `active`, running the `unarchive` action if the specification has one.

```mermaid
stateDiagram-v2
    state "active / failed / cancelled" as source
    source --> archiving: archive
    archiving --> archived
    archiving --> failed: archive action fails
    archived --> updating: restore
    updating --> active
    archived --> deleting: delete
```

### Rules and guards

| Rule | Detail |
| --- | --- |
| Archivable statuses | You can archive from `active`, `failed`, or `cancelled`. |
| Services with links | A service can only be archived when all of its links are already archived. |
| Restoring links | A link can only be restored while its parent service is `active`. |
| Deleting | Delete works on archived instances too. Deleting a service still requires removing its links first, including archived ones. |
| Duplicates | You can't create a service or link that matches an archived one (same specification, entity, and dimensions). The error names the archived instance, so you can restore it or delete it first. |

:::info
Archiving requires the ordinary **write** permission on the service, not the delete permission. Archive is recoverable, so it doesn't need the stricter permission that hard deletes require.
:::

## Archive and restore

Archiving is a status change: set the instance's `status` to `archived`, and back to `active` to restore it. You can do it from the UI, from the CLI, or with a PATCH request to the [service](/docs/api/service-update) or [link](/docs/api/link-update) API.

<Tabs
defaultValue="archive-ui"
values={[
{ label: 'UI', value: 'archive-ui' },
{ label: 'CLI', value: 'archive-cli' },
{ label: 'cURL', value: 'archive-curl' },
]}>

<TabItem value="archive-ui">

1. Go to **Platform settings** and open **Services** → **List**.
2. Find the service you want to archive and open its actions menu. **Archive** is only offered for services in `active`, `failed`, or `cancelled` status.
3. Confirm in the dialog. It asks you to type `archive`, and reminds you that all of the service's links must be archived or deleted first.

<img alt="The services list with a row's actions menu open, showing Archive between Set desired version and the destructive Mark as failed and Delete options, and the Show archived toggle above the table" src="/img/services/archive-service-menu.png" width="100%" className="helper-image" />

To restore a service, first bring the archived ones into view: turn on the **Show archived** toggle, or pick **Archived** in the status filter. The same actions menu then offers **Restore**, also behind a confirmation.

Links work the same way from the service's links list: **Archive** removes the link's parameters from the application, and **Restore** recreates them. Restore is only available while the parent service is active.

While an instance is `archiving`, its status chip shows the operation in progress and destructive actions stay disabled until it finishes.

</TabItem>

<TabItem value="archive-cli">

Archive a service by patching its `status` to `archived`:

```bash
np service patch \
  --id <SERVICE_ID> \
  --body '{ "status": "archived" }'
```

Restore it by patching the status back to `active`:

```bash
np service patch \
  --id <SERVICE_ID> \
  --body '{ "status": "active" }'
```

Links follow the same pattern with `np link patch`:

```bash
np link patch \
  --id <LINK_ID> \
  --body '{ "status": "archived" }'
```

</TabItem>

<TabItem value="archive-curl">

Archive a service by patching its `status` to `archived`:

```bash
curl -L -X PATCH 'https://api.nullplatform.com/service/<SERVICE_ID>' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer <token>' \
-d '{ "status": "archived" }'
```

Restore it by patching the status back to `active`:

```bash
curl -L -X PATCH 'https://api.nullplatform.com/service/<SERVICE_ID>' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer <token>' \
-d '{ "status": "active" }'
```

Links follow the same pattern against `/link/<LINK_ID>`:

```bash
curl -L -X PATCH 'https://api.nullplatform.com/link/<LINK_ID>' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer <token>' \
-d '{ "status": "archived" }'
```

</TabItem>
</Tabs>

If you manage your services as code, see [OpenTofu/Terraform](#opentofu-terraform) for archiving them from your configuration.

### What happens after the request

Archiving resolves in one of three ways, depending on the specification. This mirrors how delete behaves:

- **The specification has a managed `archive` action** → nullplatform creates and runs the action for you in the same request. The instance answers with status `archiving` (or `updating` on restore) and the action appears in `actions_in_progress`.
- **The specification has an unmanaged `archive` action** → the request returns `400`, and you run the archive action yourself, the same way you run any other [service action](/docs/services/craft-a-service/service-actions).
- **The specification has no `archive` action** → the status changes directly, with the same guards applied.

An archive request can't include `attributes` (there is no workflow to apply them to). Other metadata fields in the same patch apply immediately.

:::note
A restore that resolves as a direct status change enforces the specification's required attributes, like any other direct write to `active`. If an archived instance has an incomplete attribute set, restore it through the `unarchive` action, or complete its attributes first.
:::

## Filtering archived instances

The API lists archived instances like any other status, so they show up when you [list services](/docs/api/service-list) or [list links](/docs/api/link-list) until you filter them out. Hiding them is what the UI's **Show archived** toggle does for you.

To list everything except archived instances, exclude them with `status:ne`:

```bash
curl -L 'https://api.nullplatform.com/service?nrn=<NRN>&status:ne=archived' \
-H 'Authorization: Bearer <token>'
```

To list only the archived ones, filter by `status`:

```bash
curl -L 'https://api.nullplatform.com/service?nrn=<NRN>&status=archived' \
-H 'Authorization: Bearer <token>'
```

Both filters work the same way against `/link`. The `status:ne` filter combines with `status` as an AND, so `?status=archived&status:ne=archived` returns nothing. Fetching an instance by ID works regardless of its status.

Archiving also changes what the `has_links` filter returns. It counts only links that are neither archived nor deleted, so a service whose links are all archived comes back under `?has_links=false`. A link that is still `archiving` counts as a link, because the operation hasn't landed yet.

## Enable archive on your specifications

Whether archive runs a workflow depends on the service specification:

- **New specifications** created with `use_default_actions` get the `archive` action generated automatically, alongside create, update, and delete. The `unarchive` action is never auto-generated.
- **Existing specifications** are never modified automatically. You opt in explicitly by [creating the action specification](/docs/api/service-specification-actions-create), from the CLI or the API.

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

<TabItem value="optin-cli">

```bash
np service specification action specification create \
  --serviceSpecificationId <SPEC_ID> \
  --body '{
    "name": "Archive service",
    "type": "archive"
  }'
```

</TabItem>

<TabItem value="optin-curl">

```bash
curl -L -X POST 'https://api.nullplatform.com/service_specification/<SPEC_ID>/action_specification' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer <token>' \
-d '{
  "name": "Archive service",
  "type": "archive"
}'
```

</TabItem>
</Tabs>

Create a second one with `"type": "unarchive"` to run a workflow on restore too. Link specifications work the same way, through their own [action specifications](/docs/services/craft-a-service/link-action-specs).

For specifications using default actions, nullplatform generates the action's parameters and results schemas from the specification's attributes and keeps them in sync on every specification update. Sending your own `parameters` or `results` returns a `400`.

Once you've opted in, nullplatform owns the action's content: `PATCH` is refused, and `DELETE` is how you opt out. For agent-backed specifications (`use_default_actions: false`), you author both action types with your own schemas, like any other action.

:::tip
Opt in to both `archive` and `unarchive` together. If only `archive` exists, archiving runs your workflow but restoring falls back to a direct status change, so nothing ever tells your infrastructure to undo the archive.
:::

### OpenTofu/Terraform {#opentofu-terraform}

If you manage your specifications as code with the [nullplatform IaC provider](/docs/iac-provider), the opt-in is a [`nullplatform_action_specification`](https://registry.terraform.io/providers/nullplatform/nullplatform/latest/docs/resources/action_specification) resource carrying only the `name`, the `type`, and the parent specification reference. Leave `parameters` and `results` out: nullplatform generates them, and sending your own is refused.

```hcl
resource "nullplatform_action_specification" "unarchive_queue" {
  name                     = "Restore queue"
  type                     = "unarchive"
  service_specification_id = "<SPEC_ID>"
}
```

Use `link_specification_id` instead for a link specification.

Which of the two actions you declare depends on where the specification came from:

- **Created with `use_default_actions`**: the `archive` action already exists, so declaring it fails with `There is already an action of type archive`. Adopt the generated one with `terraform import` if you want it in state, and declare only `unarchive`.
- **Created before archiving existed, or with `use_default_actions: false`**: neither action is generated, so declare both.

You don't need `ignore_changes` here. The provider treats `parameters` and `results` as computed, so the schemas nullplatform generates land in state without showing up as drift. Creating the resource is the opt-in and destroying it is the opt-out, so `tofu destroy` and `terraform destroy` behave as expected: archiving falls back to a direct status change.

#### Archiving the services themselves

The provider archives service instances too, not just the specification's opt-in. Set `archive_on_destroy` and `terraform destroy` archives the service instead of deleting it, leaving the row, its attributes, and its infrastructure in place:

```hcl
resource "nullplatform_service" "orders_queue" {
  name               = "orders-queue"
  specification_id   = "<SPEC_ID>"
  entity_nrn         = "<APPLICATION_NRN>"
  archive_on_destroy = true

  timeouts {
    delete = "10m"
  }
}
```

To archive or restore on demand instead, set `status` to `archived` or `active` on a service that already exists, and give the resource an `update` timeout so the apply can wait for the transition. Leave `status` out of your configuration the rest of the time, so a service archived outside your code isn't restored by the next unrelated apply. The read-only `archived_at` attribute is available as an output.

:::warning
`archive_on_destroy` is read from state, so the apply that sets it has to run before the destroy that relies on it. Adding the flag and destroying in the same run still hard-deletes the service.
:::

The [`nullplatform_service` resource](https://registry.terraform.io/providers/nullplatform/nullplatform/latest/docs/resources/service) documents the full destroy behavior, including how `force_destroy` and `import` interact with `archive_on_destroy`.

## Implement archive in your service workflows

For agent-backed services, `archive` and `unarchive` actions run through the same workflow and in the same working directory as your create and update actions. Archive is one more state your module applies, not a separate code path: the same apply runs, and your code decides what "archived" means for the underlying resource.

A typical pattern:

1. Derive an `archived` flag in your module from the action being executed, falling back to the service's status so that a later update doesn't accidentally un-archive the resource.
2. Use that flag to apply the archived state conditionally. For example, an SQS queue module can attach a queue policy that denies sending and receiving messages while archived. The queue keeps its name, ARN, and messages; restoring removes the policy.
3. Remember that archive and unarchive actions don't carry creation parameters. If your module reads inputs from action parameters, resolve them from stored state or outputs when parameters are empty.

:::warning
Make sure every execution path of your service treats `archive` and `unarchive` as an apply. If your automation only maps known action types and defaults everything else to a plan or a no-op, an archive action will report success without touching your infrastructure.
:::

Deploy your workflow changes before you create the `archive` and `unarchive` action specifications. If the actions exist first, archiving runs an apply with no changes: the service ends up marked as archived while your infrastructure stays untouched.

## Related docs

- [Manage and provision services](/docs/services/manage-services): provision services and link them to your applications
- [Action specifications](/docs/services/craft-a-service/service-actions): define the actions a specification exposes
- [Craft a service](/docs/services/craft-a-service/overview): build and publish your own service specifications
