# Authentication

There are several ways to authenticate on the API.

## Basic Auth

We recommend basic auth only in development mode or when writing simple scripts
(the kind that does not do more than 5 requests). Because this is a slow form of
authentication.

```http
GET /_session HTTP/1.1
Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
Content-Type: application/json
Accept: application/json
```

<div class="alert alert-info text-muted" role="alert" markdown="1">

This is the equivalent with `curl`:

```
curl \
  -X GET \
  -u user@example.com:password \
  -H "accept: application/json" -H "content-type: application/json" \
  "https://api.standardn9.com/_session"
```

</div>

The output is:

```http
HTTP/1.1 200 OK
Content-Type: application/json
```

```json
{
  "ok": true,
  "userCtx": {
    "name": "usr-9fcc027ca99e44468227bef4c4440d5c",
    "roles": [
      "acc-1e36d77db5434f3cbded1bb07db403c1"
    ]
  }
}
```

## Bearer Token

Getting a token means you authenticate with a slow process, and after that you
only use the token.

Using username and password is slow because we have algorithms in place to
prevent [Timing attacks](https://en.wikipedia.org/wiki/Timing_attack)

Although, we don't have yet a mechanism to expire all of the tokens created by a
user we plan to implement it. This is another reason you should prefer to use a
token to authenticate.
{: .alert .alert-info .text-secondary }

The endpoint to get a token is `POST /_session`.

Here you can use Basic Auth for the request:

```http
POST /_session HTTP/1.1
Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
Content-Type: application/json
Accept: application/json
```

Or add the information within the JSON payload:

```http
POST /_session HTTP/1.1
Content-Type: application/json
Accept: application/json

{
  "username": "user@example.com",
  "password": "password"
}
```

In both cases the JSON output is:

```http
HTTP/1.1 200 OK
Content-Type: application/json
set-cookie: AuthSession=eyJhbGciOi; path=/; expires=Wed, 08 Feb 2023 17:59:47 GMT; HttpOnly; SameSite=Strict
```

```json
{
  "ok": true,
  "name": "usr-9fcc027ca99e44468227bef4c4440d5c",
  "roles": [
    "acc-1e36d77db5434f3cbded1bb07db403c1"
  ]
}
```

The JWT token is in the header of the HTTP request as
`set-cookie`.

To simplify the output and commands we are going to shorten the token from `eyJhbGciOi.eyJ1c2VyX2lkIjoiOWZjYzAyN2MtYTk5ZS00NDQ2LTgyMjctYmVmNGM0NDQwZDVjIn0.7GzetforEg8U1-PrsjWNELa2iMaEsD1JJNoY82vh52s` to `eyJhbGciOi`.
{: .alert .alert-info .text-secondary }

With that token you can use to authenticate the following requests.

For example, to get your session with that token:

```http
GET /_session HTTP/1.1
Authorization: Bearer eyJhbGciOi
Content-Type: application/json
Accept: application/json
```

And the output is the same as before:

```http
HTTP/1.1 200 OK
Content-Type: application/json
```

```
{
  "ok": true,
  "name": "usr-9fcc027ca99e44468227bef4c4440d5c",
  "roles": [
    "acc-1e36d77db5434f3cbded1bb07db403c1"
  ]
}
```

Here is an explanation of the attributes:

<dl>
<dt>ok</dt>
<dd>
Indicates the authentication is ok. If this is a
<code>POST</code> request the token is
expected to be in the header.
</dd>
<dt>name</dt>
<dd>
This is the unique <code>ID</code> of the user
</dd>
<dt>roles</dt>
<dd>
<code>roles</code> exposes an array with the <em>databases</em> the user has some kind of
permission, be it read and/or write.
</dd>
</dl>

# Session

## `GET /_session`

Request:

```http
GET /_session HTTP/1.1
Authorization: Bearer eyJhbGciOi
Content-Type: application/json
Accept: application/json
```

Response:

```http
HTTP/1.1 200 OK
Content-Type: application/json
```

```json
{
  "ok": true,
  "name": "usr-9fcc027ca99e44468227bef4c4440d5c",
  "roles": [
    "acc-1e36d77db5434f3cbded1bb07db403c1"
  ]
}
```

And the response in case of wrong token or username/password:

```
HTTP/1.1 401 Unauthorized
Content-Type: application/json
```

```
{
  "error": "unauthorized",
  "reason": "Name or password is incorrect."
}
```

# Database

_Database_ is what the frontend displays as _Group_. For the
purpose of this documentation we are going to call _database_.
{: .alert .alert-info .text-secondary }

A _database_ is basically an aggregation of data that several users have access
to. Some other software may call it _Group_, _Organization_, or _Team_.

_Database_ and _User_ are the only two things that do not depend on anything to
exist.

As you could see in the session output, the `roles` attribute exposes an array
with the unique name of each _database_ the user has permission.

Each user can create up to 5 free databases, we recommend using this feature to
test any API integration.

## `GET /:db_name`

Request:

```http
GET /acc-1e36d77db5434f3cbded1bb07db403c1 HTTP/1.1
Authorization: Bearer eyJhbGciOi
Content-Type: application/json
Accept: application/json
```

Response:

```http
HTTP/1.1 200 OK
Content-Type: application/json
```

```json
{
  "instance_start_time": "0",
  "db_name": "acc-afb2ce55bfdf489088273a1ab8511936",
  "update_seq": 18,
  "doc_count": 0
}
```

# CRUD operations

Besides that we also have one kind of record that let you
perform tests in the API even in a production
environment.

There is no frontend to see any of the data within the _Crystal_ records, e.g.,
this is an API-only feature.
{: .alert .alert-info .text-secondary }

This kind of record lets you manage _Crystal_,
each has the following fields:

* `_id`: Unique ID of the record
* `_rev`: The locking code lets you perform updates, _auto generated_
* `klass`: The kind/type of record, in this case, `Crystal`
* `name`: The name of the `Crystal`, because this is a testing endpoint, there is no right or wrong information to store here, but it cannot be an empty string, _Text_
* `age`: The age of the `Crystal`, the number stored in this field has to be lower than 125, _Integer_

## `POST /:db_name` - Create One Record

When you sign up your _database_ and the `_id` of your _User_ are random, we do
that to prevent _databases_ and _Users_ to reuse `_id`s as this can lead to
security vulnerabilities.

But when you manage the records within your organization you must provide the
`_id` yourself.

As you could see from the previous examples the `_id` field is a
[Universally unique identifier](https://en.wikipedia.org/wiki/Universally_unique_identifier)
plus a 3 letter prefix for that particular kind of record.

If you don't want to use a random `_id` for your records but an incremental one,
you can replace all characters with `0` except for the _prefix_ and the `-`
(dash) and place the number to the end, example:

```
crs-00000000000000000000000000000157
```

<div class="alert alert-info text-muted" role="alert" markdown="1">

If the records are managed by our frontend we are going to use random UUIDs no
matter what pattern you have been following using the API. There is no
configuration or planned change to this behavior.

</div>

Creating a record with valid data:

```http
POST /acc-1e36d77db5434f3cbded1bb07db403c1 HTTP/1.1
Authorization: Bearer eyJhbGciOi
Content-Type: application/json
Accept: application/json

{
  "_id": "crs-041fa18a2f77410d9bf4e07fa33abeeb",
  "klass": "Crystal",
  "name": "John Doe",
  "age": 120
}
```

Returns:

```http
HTTP/1.1 200 OK
Content-Type: application/json
```

```json
{
  "id": "crs-a9d89d7963a2ee2f1b92f73c2dfb73a0",
  "ok": true,
  "rev": "1-d89d7963a2ee2f1b92f73c2dfb73a0"
}
```

Creating a record with missing mandatory field:


```http
POST /acc-1e36d77db5434f3cbded1bb07db403c1 HTTP/1.1
Authorization: Bearer eyJhbGciOi
Content-Type: application/json
Accept: application/json

{
  "name": "John Doe",
  "age": 120
}
```

Returns:

```http
HTTP/1.1 422 Unprocessable Entity
Content-Type: application/json
```

```json
{
  "error": "unprocessable_entity",
  "reason": {
    "_id": "can't be blank"
  }
}
```

Creating a record with invalid data:

```http
POST /acc-1e36d77db5434f3cbded1bb07db403c1 HTTP/1.1
Authorization: Bearer eyJhbGciOi
Content-Type: application/json
Accept: application/json

{
  "_id": "crs-ee4371b193ac4b4191cb27624c7442c2",
  "name": "John Doe",
  "age": 210
}
```

Returns:

```
HTTP/1.1 422 Unprocessable Entity
Content-Type: application/json
```

```json
{
  "error": "unprocessable_entity",
  "reason": {
    "age": "less-than-or-equal-to"
  }
}
```

## `GET /:db_name/:doc_id` - Get one record

With the ID of the record to query you can call the API with:

```http
GET /acc-1e36d77db5434f3cbded1bb07db403c1/crs-ee4371b193ac4b4191cb27624c7442c2 HTTP/1.1
Authorization: Bearer eyJhbGciOi
Content-Type: application/json
Accept: application/json
```

Returns:

```http
HTTP/1.1 200 OK
Content-Type: application/json
```

```json
{
  "_id": "crs-a9d89d7963a2ee2f1b92f73c2dfb73a0",
  "_rev": "1-10257bd481d47fa40decc71cdbea0928",
  "klass": "Crystal",
  "name": "John Doe",
  "age": 120
}
```

## `PUT /:db_name/:doc_id` - Update one record

Some rules are mandatory to this endpoint:

* You have to provide a valid `rev` in the query parameters
* The document is fully replaced with the body of the request

An example of updating a _Crystal_:

```http
PUT /acc-1e36d77db5434f3cbded1bb07db403c1/crs-ee4371b193ac4b4191cb27624c7442c2?rev=1-25907e94b8cec1aa102595d415fbb36b HTTP/1.1
Authorization: Bearer eyJhbGciOi
Content-Type: application/json
Accept: application/json

{
  "klass": "Crystal",
  "name": "John Doe",
  "age":120
}
```

And the response is the same as the `POST`:

```http
HTTP/1.1 200 OK
Content-Type: application/json
```

```json
{
  "id": "crs-ee4371b193ac4b4191cb27624c7442c2",
  "ok": true,
  "rev": "2-51e28e113e600bc19bff2dea57fe45ed"
}
```

## `DELETE /:db_name/:doc_id` - Delete one record

You also need the `rev` query parameters to delete a record.

An example request to delete a record is:

```http
DELETE /acc-1e36d77db5434f3cbded1bb07db403c1/crs-ee4371b193ac4b4191cb27624c7442c2?rev=2-f32d250e7d4ce0a07c99dd2d14095582 HTTP/1.1
Authorization: Bearer eyJhbGciOi
Content-Type: application/json
Accept: application/json
```

And the response is the same as create or update:

```http
HTTP/1.1 200 OK
Content-Type: application/json
```

```json
{
  "id": "crs-ee4371b193ac4b4191cb27624c7442c2",
  "ok": true,
  "rev": "3-1cf191e2a26ce6f2648dcf52c7bb676e"
}
```

If you forget or pass on a wrong `rev` you get the same response as before:

```http
HTTP/1.1 409 Conflict
Content-Type: application/json
```

```json
{
  "error": "conflict",
  "reason": "Document update conflict."
}
```

<!--
Find multiple records with <code>POST /:db_name/_find</code>
TODO
-->

# WebDAV

We also implement a WebDAV API for you to manage your files.

## `PROPFIND /`

Querying the root of the website only requests one item, see the next section.

Request:

```http
PROPFIND /
Content-Type: application/xml
Accept: application/xml
```

Response:

```
<?xml version="1.0" encoding="UTF-8"?>
<D:multistatus xmlns:D="DAV:" xmlns:A="http://standardn9.com/webdav">
  <D:response>
    <D:href>/</D:href>
    <D:propstat>
      <D:prop>
        <D:displayname></D:displayname>
        <D:resourcetype>
          <D:collection/>
        </D:resourcetype>
      </D:prop>
      <D:status>HTTP/1.1 200 OK</D:status>
    </D:propstat>
  </D:response>
  <D:response>
    <D:href>/_webdav/</D:href>
    <D:propstat>
      <D:prop>
        <D:displayname>_webdav</D:displayname>
        <D:resourcetype>
          <D:collection/>
        </D:resourcetype>
      </D:prop>
      <D:status>HTTP/1.1 200 OK</D:status>
    </D:propstat>
  </D:response>
</D:multistatus>
```

## `PROPFIND /_webdav/`

Returns the list of database the user can access.

By default it returns with the handle of the _database_, not with the database
name, this way it is easier for humans to identify the _Group_.

Request:

```http
PROPFIND /_webdav/
Content-Type: application/xml
Accept: application/xml
```

Response:

```xml
<?xml version="1.0" encoding="UTF-8"?>
<D:multistatus xmlns:D="DAV:" xmlns:A="http://standardn9.com/webdav">
  <D:response>
    <D:href>/_webdav/</D:href>
    <D:propstat>
      <D:prop>
        <D:displayname>_webdav</D:displayname>
        <D:resourcetype>
          <D:collection/>
        </D:resourcetype>
      </D:prop>
      <D:status>HTTP/1.1 200 OK</D:status>
    </D:propstat>
  </D:response>
  <D:response>
    <D:href>/_webdav/hardware-t7mjw/</D:href>
    <D:propstat>
      <D:prop>
        <D:displayname>hardware-t7mjw</D:displayname>
        <D:resourcetype>
          <D:collection/>
        </D:resourcetype>
      </D:prop>
      <D:status>HTTP/1.1 200 OK</D:status>
    </D:propstat>
  </D:response>
  <D:response>
    <D:href>/_webdav/test-2bs/</D:href>
    <D:propstat>
      <D:prop>
        <D:displayname>test-2bs</D:displayname>
        <D:resourcetype>
          <D:collection/>
        </D:resourcetype>
      </D:prop>
      <D:status>HTTP/1.1 200 OK</D:status>
    </D:propstat>
  </D:response>
</D:multistatus>
```

## `PROPFIND /_webdav/:handle/`

This endpoint is where the list of files for each database starts.

Request:

```http
PROPFIND /_webdav/hardware-t7mjw/
Accept: application/xml
```

Response:

```http
HTTP/1.1 207 Multi-Status
Content-Type: application/xml
```

```xml
<?xml version="1.0" encoding="UTF-8"?>
<D:multistatus xmlns:D="DAV:" xmlns:A="http://standardn9.com/webdav">
  <D:response>
    <D:href>/_webdav/hardware-t7mjw/</D:href>
    <D:propstat>
      <D:prop>
        <D:displayname></D:displayname>
        <D:creationdate/>
        <D:getlastmodified/>
        <D:resourcetype>
          <D:collection/>
        </D:resourcetype>
      </D:prop>
      <D:status>HTTP/1.1 200 OK</D:status>
    </D:propstat>
  </D:response>
  <D:response>
    <D:href>/_webdav/hardware-t7mjw/pwd.txt</D:href>
    <D:propstat>
      <D:prop>
        <D:displayname>pwd.txt</D:displayname>
        <D:creationdate>Thu, 25 Jun 2026 16:08:33 GMT</D:creationdate>
        <D:getlastmodified>Thu, 25 Jun 2026 16:08:33 GMT</D:getlastmodified>
        <D:getcontentlength>9</D:getcontentlength>
        <D:resourcetype>
        </D:resourcetype>
        <A:system-id>fle-eda30114a4874349a42615837035ef14</A:system-id>
        <A:system-rev>2-d026b08b7db15661a419d339d8a094e5</A:system-rev>
      </D:prop>
      <D:status>HTTP/1.1 200 OK</D:status>
    </D:propstat>
  </D:response>
  <D:response>
    <D:href>/_webdav/hardware-t7mjw/test/</D:href>
    <D:propstat>
      <D:prop>
        <D:displayname>test</D:displayname>
        <D:creationdate>Thu, 25 Jun 2026 17:06:11 GMT</D:creationdate>
        <D:getlastmodified>Thu, 25 Jun 2026 17:06:21 GMT</D:getlastmodified>
        <D:resourcetype>
          <D:collection/>
        </D:resourcetype>
        <A:system-id>fle-3e0a0640c8204c798b0b0a06b876038e</A:system-id>
        <A:system-rev>2-1d97126853766068141a1df4b0242ff9</A:system-rev>
      </D:prop>
      <D:status>HTTP/1.1 200 OK</D:status>
    </D:propstat>
  </D:response>
</D:multistatus>
```

In this example there is one folder and one file.

## `PROPFIND /_webdav/:handle/somecol/`

Request:

```http
PROPFIND /_webdav/hardware-t7mjw/test/
Accept: application/xml
```

Response:

```http
HTTP/1.1 207 Multi-Status
Content-Type: application/xml
```

```xml
<?xml version="1.0" encoding="UTF-8"?>
<D:multistatus xmlns:D="DAV:" xmlns:A="http://standardn9.com/webdav">
  <D:response>
    <D:href>/_webdav/hardware-t7mjw/test/</D:href>
    <D:propstat>
      <D:prop>
        <D:displayname>test</D:displayname>
        <D:creationdate>Thu, 25 Jun 2026 15:42:41 GMT</D:creationdate>
        <D:getlastmodified>Thu, 25 Jun 2026 15:42:41 GMT</D:getlastmodified>
        <D:resourcetype>
          <D:collection/>
        </D:resourcetype>
        <A:system-id>fle-b725ea89059e403c8bfbdc0259037d02</A:system-id>
        <A:system-rev>1-f4bcedc8d4ed9d7a960271009c96a77a</A:system-rev>
      </D:prop>
      <D:status>HTTP/1.1 200 OK</D:status>
    </D:propstat>
  </D:response>
</D:multistatus>
```

In this example the folder is empty.

# References

* [Basic access authentication](https://en.wikipedia.org/wiki/Basic_access_authentication)
* [Universally unique identifier](https://en.wikipedia.org/wiki/Universally_unique_identifier)
* [Timing attacks](https://en.wikipedia.org/wiki/Timing_attack)
