Skip to content
Alvo is pre-v0.1: the image runs from its edge tag, and no NuGet package or release is published yet.Pre-v0.1: no release yet.Roadmap and status

Delete one 'vehicles' row

DELETE
/api/vehicles/{id}
curl --request DELETE \
--url http://localhost:8080/api/vehicles/2489E9AD-2EE2-8E00-8EC9-32D5F69181C0

Deletes one row and returns no body. A row the caller’s policy excludes is 404, exactly as an absent one is.

If-Match conditions the delete on the row’s current version, and If-None-Match is refused with 412 — both exactly as on the update, including the wording of what cannot be compared.

Idempotency-Key makes the retry answer 204. Removing one row twice always left the same state, so nothing was ever duplicated; what a retry after a lost 204 could not tell you is whether the row was yours to have removed. Without a key that retry is a 404 (or a 412) you cannot tell apart from somebody else’s delete; with one it is 204, because the key records that this caller already performed exactly this delete. The key covers the row and the If-Match too, so reusing it against another row is 409.

id
required
string format: uuid

The row’s key. A value routing cannot read as a GUID is a 404 from routing itself, before the endpoint runs; a well-formed key naming a row the caller may not see is also a 404, indistinguishable from one that does not exist.

If-Match
string

Perform the write only if the row is still at this version. One entity tag exactly as a previous response returned it, or * to require only that the row still exist. Anything else — several tags, a weak W/ tag, a value this API never minted — is 412 rather than ignored, because ignoring a precondition is the lost update the header exists to prevent.

Idempotency-Key
string
>= 1 characters <= 255 characters

Makes this write retry-safe. The result is recorded against the key and the caller’s own scope, so the same key repeated replays the first result and writes nothing further: a retried create is the first row, a retried update is the row, and a retried delete is 204 rather than a 404 the caller cannot tell from somebody else’s delete. The key covers the whole request — the method, the entity, the row it addresses, the If-Match it carries and the body — so the same key against another row, or with another If-Match, is 409 rather than a replay. An anonymous caller’s key is refused, because every anonymous caller shares one identity and their keys would share one space. The bound below is a byte bound — at most 255 bytes once UTF-8 encoded — so a key of non-ASCII characters reaches it sooner than maxLength suggests; an over-long key is refused rather than shortened, because two keys differing only past the cut would become one.

The row was deleted. No body.

Cache-Control
string

Always no-store. These representations are policy-masked per caller and the ETag is minted over the row’s version rather than over the response bytes, so no cache — shared or private — may keep the body. Keeping the tag alone is not caching a representation, which is what makes a conditional write still possible.

Example
no-store

A credential was presented and cannot be used — unknown, revoked, expired, malformed, or issued for another tenant. One wording for all of them, so key ids cannot be enumerated one request at a time. ‘WWW-Authenticate’ names the scheme and the header to send. Presenting no credential at all is not a 401: an anonymous caller is judged by policy like any other.

Media typeapplication/problem+json
problemDetails

An RFC 9457 problem document. Branch on type; detail is prose and, per §3.1.1, ought not be parsed. A type keys on the kind of refusal and never on its reason — one value covers every policy refusal, and one covers both an absent row and a row the caller may not see.

object
type
required

The classification to branch on. Every value is listed here, so a client needs no prose: the namespace is https://alvo.dev/errors/ and the slug names the kind of refusal.

string format: uri
Allowed values: https://alvo.dev/errors/validation https://alvo.dev/errors/malformed-query https://alvo.dev/errors/forbidden https://alvo.dev/errors/out-of-scope https://alvo.dev/errors/not-found https://alvo.dev/errors/precondition-failed https://alvo.dev/errors/precondition-required https://alvo.dev/errors/idempotency-conflict https://alvo.dev/errors/conflict https://alvo.dev/errors/destructive-change https://alvo.dev/errors/unauthenticated https://alvo.dev/errors/unreadable-request https://alvo.dev/errors/unsupported-media-type https://alvo.dev/errors/internal https://alvo.dev/errors/function-failed
title
required

The status code’s standard reason phrase. Carries no Alvo-specific information.

string
status
required

The HTTP status code, repeated in the body as RFC 9457 §3.1.2 allows.

integer format: int32
detail
required

What went wrong, in prose. Built from constants and server-owned values only — it never echoes a caller-supplied field name or value, because a refusal is answered before authorization on some paths and would otherwise be the cheapest oracle in the API.

string
violations

Every itemised reason the request was refused, not only the first — so one round trip is enough to repair it. Present on a refusal that has itemised reasons (a malformed query string, a body the entity’s declared shape refuses) and absent on one that does not.

Array<object>
problemViolation

One machine-readable reason a request was refused.

object
pointer
required

An RFC 6901 JSON Pointer into the request body, or the role of the query-string parameter concerned (filter, order, limit, offset, after, select). A role rather than a name, because in this filter grammar a parameter’s name is a field name.

string
code
required

A stable kebab-case code for the kind of violation, safe to branch on.

string
message
required

One sentence, free of caller-supplied text.

string
fixSuggestion

What to change. Every refusal Alvo itself raises carries one; it is nullable for a violation forwarded from a source that has none to offer, where an empty string would be indistinguishable from a blank suggestion.

null | string

Example

{
"type": "https://alvo.dev/errors/validation",
"violations": [
{
"pointer": "/name",
"code": "max-length"
}
]
}
Cache-Control
string

Always no-store. These representations are policy-masked per caller and the ETag is minted over the row’s version rather than over the response bytes, so no cache — shared or private — may keep the body. Keeping the tag alone is not caching a representation, which is what makes a conditional write still possible.

Example
no-store
WWW-Authenticate
string

The RFC 7235 challenge, naming the scheme and the request header a credential is read from — so an agent can discover how to authenticate rather than guess. The header name is host configuration, which is why the challenge states it rather than assuming a default.

The operation is refused. Two kinds, told apart by the problem ‘type’ and never by its prose: ‘out-of-scope’ means the presented key’s scopes do not cover this entity and operation (grant the key the scope), and ‘forbidden’ means policy refused it (change a rule). A policy refusal here is an operation-level one — the operation is unconfigured, the entity is unknown to the applied descriptor, the caller has no tenant on a tenant-scoped entity, or the policy reads a caller value this caller does not carry. It is never ‘your rule excluded these rows’; see the 200.

Media typeapplication/problem+json
problemDetails

An RFC 9457 problem document. Branch on type; detail is prose and, per §3.1.1, ought not be parsed. A type keys on the kind of refusal and never on its reason — one value covers every policy refusal, and one covers both an absent row and a row the caller may not see.

object
type
required

The classification to branch on. Every value is listed here, so a client needs no prose: the namespace is https://alvo.dev/errors/ and the slug names the kind of refusal.

string format: uri
Allowed values: https://alvo.dev/errors/validation https://alvo.dev/errors/malformed-query https://alvo.dev/errors/forbidden https://alvo.dev/errors/out-of-scope https://alvo.dev/errors/not-found https://alvo.dev/errors/precondition-failed https://alvo.dev/errors/precondition-required https://alvo.dev/errors/idempotency-conflict https://alvo.dev/errors/conflict https://alvo.dev/errors/destructive-change https://alvo.dev/errors/unauthenticated https://alvo.dev/errors/unreadable-request https://alvo.dev/errors/unsupported-media-type https://alvo.dev/errors/internal https://alvo.dev/errors/function-failed
title
required

The status code’s standard reason phrase. Carries no Alvo-specific information.

string
status
required

The HTTP status code, repeated in the body as RFC 9457 §3.1.2 allows.

integer format: int32
detail
required

What went wrong, in prose. Built from constants and server-owned values only — it never echoes a caller-supplied field name or value, because a refusal is answered before authorization on some paths and would otherwise be the cheapest oracle in the API.

string
violations

Every itemised reason the request was refused, not only the first — so one round trip is enough to repair it. Present on a refusal that has itemised reasons (a malformed query string, a body the entity’s declared shape refuses) and absent on one that does not.

Array<object>
problemViolation

One machine-readable reason a request was refused.

object
pointer
required

An RFC 6901 JSON Pointer into the request body, or the role of the query-string parameter concerned (filter, order, limit, offset, after, select). A role rather than a name, because in this filter grammar a parameter’s name is a field name.

string
code
required

A stable kebab-case code for the kind of violation, safe to branch on.

string
message
required

One sentence, free of caller-supplied text.

string
fixSuggestion

What to change. Every refusal Alvo itself raises carries one; it is nullable for a violation forwarded from a source that has none to offer, where an empty string would be indistinguishable from a blank suggestion.

null | string

Example

{
"type": "https://alvo.dev/errors/validation",
"violations": [
{
"pointer": "/name",
"code": "max-length"
}
]
}
Cache-Control
string

Always no-store. These representations are policy-masked per caller and the ETag is minted over the row’s version rather than over the response bytes, so no cache — shared or private — may keep the body. Keeping the tag alone is not caching a representation, which is what makes a conditional write still possible.

Example
no-store

No such row — or one the caller’s policy excludes. The two are indistinguishable on purpose, in the problem ‘type’ as much as in the prose, so a 404 cannot be used to prove a row exists.

Media typeapplication/problem+json
problemDetails

An RFC 9457 problem document. Branch on type; detail is prose and, per §3.1.1, ought not be parsed. A type keys on the kind of refusal and never on its reason — one value covers every policy refusal, and one covers both an absent row and a row the caller may not see.

object
type
required

The classification to branch on. Every value is listed here, so a client needs no prose: the namespace is https://alvo.dev/errors/ and the slug names the kind of refusal.

string format: uri
Allowed values: https://alvo.dev/errors/validation https://alvo.dev/errors/malformed-query https://alvo.dev/errors/forbidden https://alvo.dev/errors/out-of-scope https://alvo.dev/errors/not-found https://alvo.dev/errors/precondition-failed https://alvo.dev/errors/precondition-required https://alvo.dev/errors/idempotency-conflict https://alvo.dev/errors/conflict https://alvo.dev/errors/destructive-change https://alvo.dev/errors/unauthenticated https://alvo.dev/errors/unreadable-request https://alvo.dev/errors/unsupported-media-type https://alvo.dev/errors/internal https://alvo.dev/errors/function-failed
title
required

The status code’s standard reason phrase. Carries no Alvo-specific information.

string
status
required

The HTTP status code, repeated in the body as RFC 9457 §3.1.2 allows.

integer format: int32
detail
required

What went wrong, in prose. Built from constants and server-owned values only — it never echoes a caller-supplied field name or value, because a refusal is answered before authorization on some paths and would otherwise be the cheapest oracle in the API.

string
violations

Every itemised reason the request was refused, not only the first — so one round trip is enough to repair it. Present on a refusal that has itemised reasons (a malformed query string, a body the entity’s declared shape refuses) and absent on one that does not.

Array<object>
problemViolation

One machine-readable reason a request was refused.

object
pointer
required

An RFC 6901 JSON Pointer into the request body, or the role of the query-string parameter concerned (filter, order, limit, offset, after, select). A role rather than a name, because in this filter grammar a parameter’s name is a field name.

string
code
required

A stable kebab-case code for the kind of violation, safe to branch on.

string
message
required

One sentence, free of caller-supplied text.

string
fixSuggestion

What to change. Every refusal Alvo itself raises carries one; it is nullable for a violation forwarded from a source that has none to offer, where an empty string would be indistinguishable from a blank suggestion.

null | string

Example

{
"type": "https://alvo.dev/errors/validation",
"violations": [
{
"pointer": "/name",
"code": "max-length"
}
]
}
Cache-Control
string

Always no-store. These representations are policy-masked per caller and the ETag is minted over the row’s version rather than over the response bytes, so no cache — shared or private — may keep the body. Keeping the tag alone is not caching a representation, which is what makes a conditional write still possible.

Example
no-store

The request conflicts with what is already stored. Two kinds, told apart by the problem ‘type’: ‘idempotency-conflict’ means the ‘Idempotency-Key’ was already used by this caller for a different request — a different body, but also a different row or a different ‘If-Match’, because the key covers the whole request (retry the identical request to replay its result, or send a fresh key); ‘conflict’ means a constraint the database enforces refused the write — a value another record already holds on a field declared unique, or a delete another record still references through a ‘ref’ declaring onDelete: restrict. The ‘violations’ array names the field for the first of those and carries a fix suggestion for both.

Media typeapplication/problem+json
problemDetails

An RFC 9457 problem document. Branch on type; detail is prose and, per §3.1.1, ought not be parsed. A type keys on the kind of refusal and never on its reason — one value covers every policy refusal, and one covers both an absent row and a row the caller may not see.

object
type
required

The classification to branch on. Every value is listed here, so a client needs no prose: the namespace is https://alvo.dev/errors/ and the slug names the kind of refusal.

string format: uri
Allowed values: https://alvo.dev/errors/validation https://alvo.dev/errors/malformed-query https://alvo.dev/errors/forbidden https://alvo.dev/errors/out-of-scope https://alvo.dev/errors/not-found https://alvo.dev/errors/precondition-failed https://alvo.dev/errors/precondition-required https://alvo.dev/errors/idempotency-conflict https://alvo.dev/errors/conflict https://alvo.dev/errors/destructive-change https://alvo.dev/errors/unauthenticated https://alvo.dev/errors/unreadable-request https://alvo.dev/errors/unsupported-media-type https://alvo.dev/errors/internal https://alvo.dev/errors/function-failed
title
required

The status code’s standard reason phrase. Carries no Alvo-specific information.

string
status
required

The HTTP status code, repeated in the body as RFC 9457 §3.1.2 allows.

integer format: int32
detail
required

What went wrong, in prose. Built from constants and server-owned values only — it never echoes a caller-supplied field name or value, because a refusal is answered before authorization on some paths and would otherwise be the cheapest oracle in the API.

string
violations

Every itemised reason the request was refused, not only the first — so one round trip is enough to repair it. Present on a refusal that has itemised reasons (a malformed query string, a body the entity’s declared shape refuses) and absent on one that does not.

Array<object>
problemViolation

One machine-readable reason a request was refused.

object
pointer
required

An RFC 6901 JSON Pointer into the request body, or the role of the query-string parameter concerned (filter, order, limit, offset, after, select). A role rather than a name, because in this filter grammar a parameter’s name is a field name.

string
code
required

A stable kebab-case code for the kind of violation, safe to branch on.

string
message
required

One sentence, free of caller-supplied text.

string
fixSuggestion

What to change. Every refusal Alvo itself raises carries one; it is nullable for a violation forwarded from a source that has none to offer, where an empty string would be indistinguishable from a blank suggestion.

null | string

Example

{
"type": "https://alvo.dev/errors/validation",
"violations": [
{
"pointer": "/name",
"code": "max-length"
}
]
}
Cache-Control
string

Always no-store. These representations are policy-masked per caller and the ETag is minted over the row’s version rather than over the response bytes, so no cache — shared or private — may keep the body. Keeping the tag alone is not caching a representation, which is what makes a conditional write still possible.

Example
no-store

A precondition this API cannot evaluate, or one that did not hold. A header it cannot compare is refused rather than ignored: ignoring it would be the lost update ‘If-Match’ exists to prevent, and the caller would read the success as proof it did not happen.

Media typeapplication/problem+json
problemDetails

An RFC 9457 problem document. Branch on type; detail is prose and, per §3.1.1, ought not be parsed. A type keys on the kind of refusal and never on its reason — one value covers every policy refusal, and one covers both an absent row and a row the caller may not see.

object
type
required

The classification to branch on. Every value is listed here, so a client needs no prose: the namespace is https://alvo.dev/errors/ and the slug names the kind of refusal.

string format: uri
Allowed values: https://alvo.dev/errors/validation https://alvo.dev/errors/malformed-query https://alvo.dev/errors/forbidden https://alvo.dev/errors/out-of-scope https://alvo.dev/errors/not-found https://alvo.dev/errors/precondition-failed https://alvo.dev/errors/precondition-required https://alvo.dev/errors/idempotency-conflict https://alvo.dev/errors/conflict https://alvo.dev/errors/destructive-change https://alvo.dev/errors/unauthenticated https://alvo.dev/errors/unreadable-request https://alvo.dev/errors/unsupported-media-type https://alvo.dev/errors/internal https://alvo.dev/errors/function-failed
title
required

The status code’s standard reason phrase. Carries no Alvo-specific information.

string
status
required

The HTTP status code, repeated in the body as RFC 9457 §3.1.2 allows.

integer format: int32
detail
required

What went wrong, in prose. Built from constants and server-owned values only — it never echoes a caller-supplied field name or value, because a refusal is answered before authorization on some paths and would otherwise be the cheapest oracle in the API.

string
violations

Every itemised reason the request was refused, not only the first — so one round trip is enough to repair it. Present on a refusal that has itemised reasons (a malformed query string, a body the entity’s declared shape refuses) and absent on one that does not.

Array<object>
problemViolation

One machine-readable reason a request was refused.

object
pointer
required

An RFC 6901 JSON Pointer into the request body, or the role of the query-string parameter concerned (filter, order, limit, offset, after, select). A role rather than a name, because in this filter grammar a parameter’s name is a field name.

string
code
required

A stable kebab-case code for the kind of violation, safe to branch on.

string
message
required

One sentence, free of caller-supplied text.

string
fixSuggestion

What to change. Every refusal Alvo itself raises carries one; it is nullable for a violation forwarded from a source that has none to offer, where an empty string would be indistinguishable from a blank suggestion.

null | string

Example

{
"type": "https://alvo.dev/errors/validation",
"violations": [
{
"pointer": "/name",
"code": "max-length"
}
]
}
Cache-Control
string

Always no-store. These representations are policy-masked per caller and the ETag is minted over the row’s version rather than over the response bytes, so no cache — shared or private — may keep the body. Keeping the tag alone is not caching a representation, which is what makes a conditional write still possible.

Example
no-store