Documentation

Making Requests

The conventions every Davi endpoint shares: base paths, slugs, bearer tokens, list and error envelopes, and how the API evolves.

Every Davi endpoint follows the same conventions, so a client written against them handles each new endpoint the same way.

v1 is in preview. The API reports its version as 1.0.0-preview and 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 pathWhat it is
/api/v1The API
/oauth2The 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"
}
  • code is stable and machine-readable. Branch on it.
  • message is written for a person and may be reworded at any time. Do not use it as an identifier.
  • errors carries structured detail about the refusal. For a validation failure it maps field names to messages, with _root holding 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