Create, update & delete
Insert, update and delete rows through a generated API - POST, PUT, PATCH and DELETE, request bodies, required roles, status codes and error responses.
| Route | Does | Role needed | Success |
|---|---|---|---|
POST /api/<schema>/<table> |
Insert one row | Read & Write or Admin | 201 {"id": <new key>} |
PUT /api/<schema>/<table>/<id> |
Update the columns you send | Read & Write or Admin | 204, empty body |
PATCH /api/<schema>/<table>/<id> |
Update the columns you send, strictly validated | Read & Write or Admin | 204, empty body |
DELETE /api/<schema>/<table>/<id> |
Delete one row | Admin | 204, empty body |
Bodies are JSON objects whose keys are column names. The examples assume the setup from
Authentication, with a Read & Write key (and an Admin key for
DELETE).
Create
curl -s -X POST "$RESTWARD_API_URL/api/public/artist" \
-H "X-API-Key: $RESTWARD_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name": "The Harness Band"}'const res = await fetch(`${process.env.RESTWARD_API_URL}/api/public/artist`, {
method: "POST",
headers: { ...headers, "Content-Type": "application/json" },
body: JSON.stringify({ name: "The Harness Band" }),
});
const { id } = await res.json(); // res.status === 201res = session.post(f"{api}/api/public/artist", json={"name": "The Harness Band"})
new_id = res.json()["id"] # res.status_code == 201var res = await http.PostAsJsonAsync("/api/public/artist", new { name = "The Harness Band" });
var created = await res.Content.ReadFromJsonAsync<Dictionary<string, JsonElement>>(); // 201
var id = created!["id"].GetInt32();HTTP/1.1 201
Content-Type: application/json
{"id":277}- The response carries the new primary-key value only. Read the row back with
GETif you need the whole record. - Generated columns are the database's. Identity / auto-increment keys, sequence defaults and
generated columns are filled by the database; a value you send for them is ignored, on create
and on update. The OpenAPI document marks them
readOnly. - Keys that are not columns of the table are ignored.
Update
PUT changes only the columns in the body; other columns keep their values. The primary key
cannot be changed.
curl -s -X PUT "$RESTWARD_API_URL/api/public/artist/277" \
-H "X-API-Key: $RESTWARD_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name": "The Harness Band (Remastered)"}'const res = await fetch(`${process.env.RESTWARD_API_URL}/api/public/artist/277`, {
method: "PUT",
headers: { ...headers, "Content-Type": "application/json" },
body: JSON.stringify({ name: "The Harness Band (Remastered)" }),
}); // 204, or 404 when the row does not existres = session.put(f"{api}/api/public/artist/277", json={"name": "The Harness Band (Remastered)"})
# 204, or 404 when the row does not existvar res = await http.PutAsJsonAsync("/api/public/artist/277", new { name = "The Harness Band (Remastered)" });
// 204 NoContent, or 404 NotFound when the row does not existHTTP/1.1 204PATCH or PUT?
Both change only the columns in the body. They differ in how strictly the body is checked:
| Body contains | PUT |
PATCH |
|---|---|---|
| An unknown column | ignored | 400 naming the column |
| The primary key | ignored | 400 (the key cannot be changed) |
| A generated column (identity, generated value) | ignored | 400 |
Nothing ({}) |
400 |
400 |
Use PATCH when a typo should fail loudly:
curl -s -X PATCH "$RESTWARD_API_URL/api/public/album/348" \
-H "X-API-Key: $RESTWARD_API_KEY" \
-H "Content-Type: application/json" \
-d '{"title": "Patch Sessions (Live)"}'const res = await fetch(`${process.env.RESTWARD_API_URL}/api/public/album/348`, {
method: "PATCH",
headers: { ...headers, "Content-Type": "application/json" },
body: JSON.stringify({ title: "Patch Sessions (Live)" }),
}); // 204, 400 for a column that cannot be updated, 404 when the row does not existres = session.patch(f"{api}/api/public/album/348", json={"title": "Patch Sessions (Live)"})var res = await http.PatchAsJsonAsync("/api/public/album/348", new { title = "Patch Sessions (Live)" });PATCH /api/public/album/348
{"album_id": 1}
HTTP/1.1 400
{"error":"Column 'album_id' is part of the primary key and cannot be changed.","column":"album_id"}Delete
Deleting requires an Admin key.
curl -s -X DELETE "$RESTWARD_API_URL/api/public/artist/277" \
-H "X-API-Key: $RESTWARD_ADMIN_KEY"const res = await fetch(`${process.env.RESTWARD_API_URL}/api/public/artist/277`, {
method: "DELETE",
headers: { "X-API-Key": process.env.RESTWARD_ADMIN_KEY },
}); // 204, or 404 when the row does not existres = requests.delete(
f"{api}/api/public/artist/277",
headers={"X-API-Key": os.environ["RESTWARD_ADMIN_KEY"]},
) # 204, or 404using var admin = new HttpClient { BaseAddress = http.BaseAddress };
admin.DefaultRequestHeaders.Add("X-API-Key", Environment.GetEnvironmentVariable("RESTWARD_ADMIN_KEY"));
var res = await admin.DeleteAsync("/api/public/artist/277"); // 204, or 404HTTP/1.1 204Deleting the same row again returns 404 with an empty body.
Errors
Error bodies have an error message and, when known, the constraint or column involved.
| Situation | Status | Body |
|---|---|---|
| Body is not valid JSON, or not a JSON object | 400 |
{"error":"The request body is not valid JSON."} / {"error":"The request body must be a JSON object."} |
PUT with no column that can be updated |
400 |
{"error":"The request body has no column of 'artist' that can be updated."} |
PATCH with an empty body, or an unknown / key / generated column |
400 |
{"error":"Unknown column 'genre' on 'album'.","column":"genre"} |
| Key role too low (Read Only writing, Read & Write deleting) | 403 |
{"error":"API key role 'ReadWrite' is not authorized for this operation. Requires 'Admin' or higher."} |
PUT/DELETE on a key that does not exist |
404 |
empty |
| Duplicate key or unique value | 409 |
{"error":"A row with the same unique value already exists (constraint 'playlist_track_pkey').","constraint":"playlist_track_pkey"} |
DELETE of a row other rows still reference |
409 |
{"error":"The row is still referenced by other rows (constraint 'album_artist_id_fkey').","constraint":"album_artist_id_fkey"} |
| Body larger than 1 MB | 413 |
{"error":"Request body exceeds maximum allowed size of 1MB."} |
Value of the wrong type ("artist_id": "one") |
422 |
{"error":"Value 'one' is not valid for column 'artist_id' of type 'integer'.","column":"artist_id"} |
Required column missing or null |
422 |
{"error":"Column 'title' is required.","column":"title"} |
| Reference to a row that does not exist | 422 |
{"error":"A referenced row does not exist (constraint 'album_artist_id_fkey').","constraint":"album_artist_id_fkey"} |
| Check constraint violated | 422 |
{"error":"A value violates a check constraint (constraint '…').","constraint":"…"} |
| Value too long, out of range or wrongly formatted for the database | 422 |
{"error":"A value is not valid for its column (too long, out of range or wrongly formatted)."} |
| Any other database error | 500 |
{"error":"A database error occurred while creating the record."} (or updating / deleting) |
Constraint and column names come from your schema; the database's own error message is never returned. Required columns and referenced keys are in the table's schema in the OpenAPI document.