---
title: "Creating a REST API · RUAL Documentation"
description: "A complete CRUD API with validation, paging and error replies."
canonical: https://docs.rual.nl/tutorials/rest-api
language: en
---

# Creating a REST API

A complete resource API with list, read, create, update, and delete: proper methods, statuses, validation, and scopes.

This tutorial builds a full REST resource: `GET/POST /api/v1/tasks` and `GET/PUT/DELETE /api/v1/tasks/{guid}`. It assumes the quickstart. If you haven't registered an endpoint before, do [Build Your First API](https://docs.rual.nl/cluster/api-quickstart) first.

### The Shape of a Resource

| Operation | Method + URI | Success status |
| --- | --- | --- |
| List | `GET /api/v1/tasks` | 200 + array |
| Create | `POST /api/v1/tasks` | 201 + created document |
| Read one | `GET /api/v1/tasks/{guid}` | 200 + document, 404 if unknown |
| Update | `PUT /api/v1/tasks/{guid}` | 200 + updated document, 404 if unknown |
| Delete | `DELETE /api/v1/tasks/{guid}` | 200 or 204, 404 if unknown |

Each row is one API block ([`on_startup_register_uri_get`](https://docs.rual.nl/block-types/http%20connection/on_startup_register_uri_get), `_post`, `_put`, `_delete`) feeding one handler function. The same trigger → work → reply shape from [Block Templates](https://docs.rual.nl/block-types/block-templates#api-endpoint).

### GET /tasks, List with Filters

- Trigger [`on_startup_register_uri_get`](https://docs.rual.nl/block-types/http%20connection/on_startup_register_uri_get) with URI `tasks` → handler function.

- Build the query: optional `status` param from [`httpconnection_get_params`](https://docs.rual.nl/block-types/http%20connection/httpconnection_get_params) → [`query_bool_term_fields`](https://docs.rual.nl/block-types/query/query_bool_term_fields) → [`query_bool_filter`](https://docs.rual.nl/block-types/query/query_bool_filter) → `query_and`, with [`query_sort_field`](https://docs.rual.nl/block-types/query/query_sort_field) on `_meta.created` descending.

- `function_search` on the `tasks` storage; respect `limit`/`offset` params for pagination.

- Reply with [`httpconnection_set_json`](https://docs.rual.nl/block-types/json/httpconnection_set_json): code 200, data = the results array.

### POST /tasks, Create with Validation

- Trigger [`on_startup_register_uri_post`](https://docs.rual.nl/block-types/http%20connection/on_startup_register_uri_post) with URI `tasks`.

- Validate required fields from the request body with [`condition_not_empty_value`](https://docs.rual.nl/block-types/condition/condition_not_empty_value) per field → `branch`; on the false path reply 400 with `{"error": "title is required"}` (see [validation template](https://docs.rual.nl/block-types/block-templates#data-validation)).

- Map the body to document fields with [`mutations_set_bp_field_multiple`](https://docs.rual.nl/block-types/mutations/mutations_set_bp_field_multiple), then [`function_create_document_from_mutations`](https://docs.rual.nl/block-types/storage/function_create_document_from_mutations).

- Reply 201 with the created document (it now has its `_meta.guid`).

### GET /tasks/{guid}, Read One

- The URI block exposes the guid as a param; fetch with [`function_get_document`](https://docs.rual.nl/block-types/storage/function_get_document) on the tasks storage.

- On its `found` condition: reply 200 with the document. Otherwise: reply 404 with `{"error": "not found"}`. Wire both paths from the success/error pins (see [error handling](https://docs.rual.nl/blueprints/block-execution#error-handling)).

### PUT and DELETE

- **PUT**: same fetch-first guard, then [`function_update_document_mutations`](https://docs.rual.nl/block-types/storage/function_update_document_mutations) with the changed fields. Reply 200 with the updated document; 404 when missing.

- **DELETE**: soft-delete with [`function_remove_document`](https://docs.rual.nl/block-types/storage/function_remove_document) (recoverable), or hard-delete with [`function_delete_document`](https://docs.rual.nl/block-types/storage/function_delete_document) when you mean it. See [document lifecycle](https://docs.rual.nl/blueprints/storage#document-lifecycle). Reply 200 with a small confirmation object.

### Scopes and Testing

- Scope the collection endpoints with the lock icon: `*public` for open reads, `*loggedin` or a custom scope for writes. See [Remote Access Control](https://docs.rual.nl/blueprints/remote-access-control).

- Activate the blueprint, then test every row of the table with curl: commands in [API workflows](https://docs.rual.nl/cluster/api-guide#workflows).

- When the endpoint 404s right after activation, it's almost always a scope or an inactive blueprint. See [Common Issues](https://docs.rual.nl/troubleshooting/common-issues#api-not-activating).

### Next Steps
