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

Read one 'owners' row

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

Reads one row by id.

A row the caller’s policy excludes reads exactly like one that was never there: 404, with the same problem type and the same prose, so the status cannot be used to prove a row exists.

If-None-Match is honoured: a caller who already holds the current version is answered 304 with the ETag and no body. Comparison is RFC 9110 §13.1.2’s weak one, so a W/ prefix is ignored — deliberately not the strong comparison If-Match gets on a write.

If-Match is ignored on a read. On a GET, RFC 9110 §13.1.1 means “send the body only if it is still this version”, which saves a caller nothing they cannot get by comparing the ETag they were sent — so the header is neither honoured nor refused here. That is the read side’s asymmetry with the write side, and it is safe for one reason: an unhonoured precondition on a read costs a response body the caller said they already had, where on a write it would cost somebody their change.

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-None-Match
string

Read the row only if it is no longer at one of these versions; otherwise 304 with no body. Compared with RFC 9110 §13.1.2’s weak comparison, so a W/ prefix is ignored — deliberately not the strong comparison If-Match gets on a write.

The row.

Media typeapplication/json
owners

A person or company a vehicle is registered to.

One row as a single read, a create or an update returns it. Every field is present, even one with no value — that field is present and null. Fields marked read-only are written by the framework and refused in a request body.

object
id
required
string format: uuid
name
required
string
<= 120 characters
email
required
null | string format: email
/^(?:[^@\s]+@[^@\s.]+(\.[^@\s.]+)+)$/
phone
required
null | string format: phone
/^(?:\+?[0-9][0-9 ()./\-]{3,30})$/
created_at
required
string format: date-time
created_by
required
null | string format: uuid
updated_at
required
string format: date-time
updated_by
required
null | string format: uuid

Example

{
"id": "3f8d6c1e-9b47-4a5f-8c21-0d7e5a2b6f04",
"email": "someone@example.com",
"phone": "+421 900 123 456",
"created_at": "2026-01-31T09:30:00+00:00",
"created_by": "3f8d6c1e-9b47-4a5f-8c21-0d7e5a2b6f04",
"updated_at": "2026-01-31T09:30:00+00:00",
"updated_by": "3f8d6c1e-9b47-4a5f-8c21-0d7e5a2b6f04"
}
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
ETag
string

The row’s version as a strong entity tag. Send it back verbatim as If-Match to make a later write conditional; it is compared octet-for-octet, so it must not be reformatted. Absent when the row has no version this caller can be given.

Example
"638712345678900000"

The caller’s ‘If-None-Match’ covers the current version, so the body is omitted. The ‘ETag’ is repeated (RFC 9110 §15.4.5) so a client need not re-read the row to get its tag back.

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
ETag
string

The row’s version as a strong entity tag. Send it back verbatim as If-Match to make a later write conditional; it is compared octet-for-octet, so it must not be reformatted. Absent when the row has no version this caller can be given.

Example
"638712345678900000"

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