Skip to content

Commit 146d48c

Browse files
author
Gianluca Lefebvre
committed
add: API usage docs including filtering and sorting syntax
1 parent 6b495c5 commit 146d48c

1 file changed

Lines changed: 221 additions & 0 deletions

File tree

docs/API-usage.md

Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
# Secrets API — Listing, Filtering & Ordering
2+
3+
How to authenticate against the TeamVault REST API and query the secrets list
4+
endpoint with filters and ordering.
5+
6+
## Base URL
7+
8+
```
9+
https://<your-teamvault-host>/api/
10+
```
11+
12+
All paths below are relative to that prefix, e.g. `GET /api/secrets/`.
13+
14+
## Authentication
15+
16+
The API uses **HTTP Basic Auth** with your TeamVault **username and password**:
17+
18+
```
19+
Authorization: Basic base64(username:password)
20+
```
21+
22+
Every endpoint requires authentication (`IsAuthenticated`); unauthenticated
23+
requests get `401 Unauthorized`.
24+
25+
> **Note on Google sign-in.** Basic Auth validates against your *local*
26+
> TeamVault password. If your account signs in through Google SSO and has no
27+
> local password set, Basic Auth will fail with `401` — ask an administrator to
28+
> set a password on your account, or call the API from a browser session
29+
> (session-cookie auth) instead.
30+
31+
### Examples
32+
33+
**curl**
34+
35+
```bash
36+
curl -u "username:password" https://teamvault.example.com/api/secrets/
37+
```
38+
39+
**Python (`requests`)**
40+
41+
```python
42+
import requests
43+
44+
BASE = "https://teamvault.example.com"
45+
AUTH = ("username", "password")
46+
47+
r = requests.get(f"{BASE}/api/secrets/", auth=AUTH)
48+
r.raise_for_status()
49+
print(r.json())
50+
```
51+
52+
## Listing secrets
53+
54+
```
55+
GET /api/secrets/
56+
```
57+
58+
Returns only secrets **visible to the authenticated user** (those that are
59+
discoverable/public or explicitly shared with you; deleted secrets are never
60+
returned).
61+
62+
### Response shape
63+
64+
Results are paginated (`PageNumberPagination`, **25 per page**):
65+
66+
```json
67+
{
68+
"count": 42,
69+
"next": "https://teamvault.example.com/api/secrets/?page=2",
70+
"previous": null,
71+
"results": [
72+
{
73+
"api_url": "https://teamvault.example.com/api/secrets/aB3dE7gH/",
74+
"web_url": "https://teamvault.example.com/secrets/aB3dE7gH/",
75+
"name": "production-db",
76+
"description": "",
77+
"username": "app",
78+
"url": "https://db.example.com",
79+
"content_type": "password",
80+
"status": "ok",
81+
"access_policy": "discoverable",
82+
"needs_changing_on_leave": true,
83+
"created": "2026-01-15T09:30:00Z",
84+
"created_by": "alice",
85+
"last_read": "2026-06-18T08:00:00Z",
86+
"current_revision": "https://teamvault.example.com/api/secret-revisions/xY9z/",
87+
"data_readable": true
88+
}
89+
]
90+
}
91+
```
92+
93+
`data_readable` indicates whether *you* may read the secret's actual data
94+
(password/file/card). The list never contains the secret value itself — fetch
95+
it via `current_revision``…/data`.
96+
97+
Get the next page with `?page=N`.
98+
99+
## Filtering
100+
101+
Pass filters as query parameters. Combining multiple filters is **AND**
102+
(all must match). Filters only ever narrow the set of secrets you can already see.
103+
104+
| Parameter | Type | Matching | Example |
105+
| ------------------------- | ------------------- | --------------- | -------------------------------- |
106+
| `name` | string | case-insensitive *contains* | `?name=prod` |
107+
| `url` | string | case-insensitive *contains* | `?url=example.com` |
108+
| `username` | string | case-insensitive *contains* | `?username=app` |
109+
| `created_by` | string (username) | case-insensitive *contains* | `?created_by=alice` |
110+
| `content_type` | choice | exact | `?content_type=password` |
111+
| `status` | choice | exact | `?status=ok` |
112+
| `access_policy` | choice | exact | `?access_policy=hidden` |
113+
| `needs_changing_on_leave` | boolean | exact | `?needs_changing_on_leave=true` |
114+
115+
### Allowed choice values
116+
117+
| Parameter | Values |
118+
| ---------------- | ------------------------------------------- |
119+
| `content_type` | `password`, `cc`, `file` |
120+
| `status` | `ok`, `needs_changing` |
121+
| `access_policy` | `any`, `discoverable`, `hidden` |
122+
123+
Notes:
124+
125+
- `status=deleted` is **rejected with `400`** — deleted secrets are never
126+
listed, so it is not a selectable value.
127+
- Any other unknown choice value also returns `400 Bad Request`.
128+
- `created_by`, `name`, `url`, `username` are *substring* matches — e.g.
129+
`created_by=admin` also matches `administrator`.
130+
131+
### Examples
132+
133+
```bash
134+
# Password secrets that still need changing
135+
curl -u "username:password" \
136+
"https://teamvault.example.com/api/secrets/?content_type=password&status=needs_changing"
137+
138+
# Everything created by alice with "db" in the name
139+
curl -u "username:password" \
140+
"https://teamvault.example.com/api/secrets/?created_by=alice&name=db"
141+
```
142+
143+
## Ordering
144+
145+
Use `ordering=<field>`. Prefix the field with `-` for descending order.
146+
147+
```
148+
GET /api/secrets/?ordering=name # A → Z
149+
GET /api/secrets/?ordering=-created # newest first
150+
```
151+
152+
Allowed ordering fields:
153+
154+
| Field | Meaning |
155+
| -------------- | -------------------------------- |
156+
| `name` | secret name |
157+
| `created` | creation time |
158+
| `last_changed` | last time the secret was changed |
159+
| `last_read` | last time the secret was read |
160+
161+
Any other field is ignored/rejected. Ordering is stable across pages (ties are
162+
broken deterministically), so paging never skips or duplicates rows.
163+
164+
## Full-text search
165+
166+
Independent of the filters above, `search` runs TeamVault's search over name,
167+
URL, username, filename and full-text index:
168+
169+
```
170+
GET /api/secrets/?search=database
171+
```
172+
173+
`search` can be combined with `ordering` and the filters:
174+
175+
```bash
176+
curl -u "username:password" \
177+
"https://teamvault.example.com/api/secrets/?search=database&status=ok&ordering=-last_read"
178+
```
179+
180+
## Putting it together (Python)
181+
182+
```python
183+
import requests
184+
185+
BASE = "https://teamvault.example.com"
186+
AUTH = ("username", "password")
187+
188+
189+
def list_secrets(**params):
190+
"""Yield every secret across all pages, applying filters/ordering."""
191+
url = f"{BASE}/api/secrets/"
192+
while url:
193+
r = requests.get(url, params=params, auth=AUTH)
194+
r.raise_for_status()
195+
body = r.json()
196+
yield from body["results"]
197+
url, params = body["next"], None # 'next' already carries the params
198+
199+
200+
# Stale password secrets, oldest-read first
201+
for secret in list_secrets(
202+
content_type="password",
203+
status="needs_changing",
204+
ordering="last_read",
205+
):
206+
print(secret["name"], secret["last_read"])
207+
```
208+
209+
## Quick reference
210+
211+
| Query param | Purpose |
212+
| --------------------------- | -------------------------------------------------- |
213+
| `name` / `url` / `username` | substring filter (case-insensitive) |
214+
| `created_by` | substring filter on creator's username |
215+
| `content_type` | `password` \| `cc` \| `file` |
216+
| `status` | `ok` \| `needs_changing` |
217+
| `access_policy` | `any` \| `discoverable` \| `hidden` |
218+
| `needs_changing_on_leave` | `true` \| `false` |
219+
| `ordering` | `name`, `created`, `last_changed`, `last_read` (prefix `-` for desc) |
220+
| `search` | full-text search term |
221+
| `page` | page number (25 results per page) |

0 commit comments

Comments
 (0)