Every Davi endpoint follows the same conventions, so a client written against them handles each new endpoint the same way.
v1is in preview. The API reports its version as1.0.0-previewand is not yet frozen. The surface is still feature-incomplete, so paths and fields can change until the freeze.
Base paths
The API has two base paths, both served from https://api.davi.social:
| Base path | What it is |
|---|---|
/api/v1 | The API |
/oauth2 | The authorization server: auth, discovery, scope metadata |
Resources are addressed by slug
Most resources are named by a human-readable slug: organizations, activities, sessions, reward templates, card models.
GET /api/v1/organizations/acme-inc
GET /api/v1/organizations/acme-inc/membership-tiers
Nesting follows ownership. A tier belongs to an organization, so it lives under
it. A resource that is reachable on its own also has a top-level path, which is
why a reward template is at /organizations/{slug}/rewards when you list an
organization's and at /rewards/{slug} when you already know which one you want.
Cards are addressed by UUID. A card's identifier is printed on a physical object and must not change when its owner renames anything.
A UUID is still accepted where a slug is expected, but that is transitional. Use slugs.
Every request carries a bearer token
Authentication is a header on every call. There is no session state.
Authorization: Bearer <access_token>
Content-Type: application/json
The token identifies both the user and the application acting for them. Two checks follow from that and both must pass: the token's scopes say what the app was allowed to ask for, and the user's role says what they can actually reach.
Sign-in covers getting a token, and Acting as an Organization covers acting for an organization.
Lists share one envelope
Every list endpoint pages the same way and returns the same wrapper:
{
"total_items": 128,
"total_pages": 7,
"current_page": 1,
"items": [ /* ... */ ]
}
Page with page and page_size. The parameters and their limits are in
Errors and Rate Limits.
A short or empty page is not the end of the list. Some endpoints page over an
underlying record set and then filter, so a page can hold fewer than page_size
items, or none, while later pages still hold items. Keep going while
current_page is below total_pages.
Errors share one envelope
Every error carries the same three fields:
{
"errors": { "email": "Enter a valid email address." },
"message": "Validation failed.",
"code": "validation_error"
}
codeis stable and machine-readable. Branch on it.messageis written for a person and may be reworded at any time. Do not use it as an identifier.errorscarries structured detail about the refusal. For a validation failure it maps field names to messages, with_rootholding anything not tied to one field. Other refusals use it to name what was missing, so do not assume every key is an input.
Some refusals carry a code narrower than the status implies: three different
403s mean join, upgrade, and wrong actor. They are listed in
Errors and Rate Limits.
The OAuth 2.0 endpoints use the RFC 6749 shape instead
({ "error": "...", "error_description": "..." }), because the specification
requires it. The status codes and code values are in
Errors and Rate Limits.
Additive changes
While v1 is in preview, these are additive and can appear in any release: new
response fields, new optional parameters, new enum values, new endpoints. Field
order is never meaningful, including in lists without an explicit sort.
Ignore what you do not recognize. A client that rejects an unknown field or throws on an unfamiliar enum value breaks on a release that was not supposed to affect it.
Next
- Errors and Rate Limits: the status codes, buckets and headers.
- Core Concepts: how users, cards, wallets and organizations relate.