llmapi-tracker 1.0.6__tar.gz → 1.0.8__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. llmapi_tracker-1.0.8/PKG-INFO +294 -0
  2. llmapi_tracker-1.0.8/README.md +262 -0
  3. llmapi_tracker-1.0.8/backend/app/launcher.py +44 -0
  4. {llmapi_tracker-1.0.6 → llmapi_tracker-1.0.8}/backend/app/schemas.py +11 -2
  5. {llmapi_tracker-1.0.6 → llmapi_tracker-1.0.8}/backend/app/services/usage_service.py +6 -1
  6. llmapi_tracker-1.0.6/backend/app/static/assets/index-jlcpybVb.js → llmapi_tracker-1.0.8/backend/app/static/assets/index-M74b_d8l.js +1 -1
  7. {llmapi_tracker-1.0.6 → llmapi_tracker-1.0.8}/backend/app/static/index.html +1 -1
  8. llmapi_tracker-1.0.8/llmapi_tracker.egg-info/PKG-INFO +294 -0
  9. {llmapi_tracker-1.0.6 → llmapi_tracker-1.0.8}/llmapi_tracker.egg-info/SOURCES.txt +2 -1
  10. {llmapi_tracker-1.0.6 → llmapi_tracker-1.0.8}/pyproject.toml +8 -6
  11. llmapi_tracker-1.0.8/tests/test_launcher.py +13 -0
  12. {llmapi_tracker-1.0.6 → llmapi_tracker-1.0.8}/tests/test_routes.py +18 -0
  13. llmapi_tracker-1.0.6/PKG-INFO +0 -234
  14. llmapi_tracker-1.0.6/README.md +0 -202
  15. llmapi_tracker-1.0.6/backend/app/launcher.py +0 -21
  16. llmapi_tracker-1.0.6/llmapi_tracker.egg-info/PKG-INFO +0 -234
  17. {llmapi_tracker-1.0.6 → llmapi_tracker-1.0.8}/LICENSE +0 -0
  18. {llmapi_tracker-1.0.6 → llmapi_tracker-1.0.8}/backend/__init__.py +0 -0
  19. {llmapi_tracker-1.0.6 → llmapi_tracker-1.0.8}/backend/app/__init__.py +0 -0
  20. {llmapi_tracker-1.0.6 → llmapi_tracker-1.0.8}/backend/app/database.py +0 -0
  21. {llmapi_tracker-1.0.6 → llmapi_tracker-1.0.8}/backend/app/frontend.py +0 -0
  22. {llmapi_tracker-1.0.6 → llmapi_tracker-1.0.8}/backend/app/main.py +0 -0
  23. {llmapi_tracker-1.0.6 → llmapi_tracker-1.0.8}/backend/app/models.py +0 -0
  24. {llmapi_tracker-1.0.6 → llmapi_tracker-1.0.8}/backend/app/paths.py +0 -0
  25. {llmapi_tracker-1.0.6 → llmapi_tracker-1.0.8}/backend/app/routes/__init__.py +0 -0
  26. {llmapi_tracker-1.0.6 → llmapi_tracker-1.0.8}/backend/app/routes/analytics.py +0 -0
  27. {llmapi_tracker-1.0.6 → llmapi_tracker-1.0.8}/backend/app/routes/models.py +0 -0
  28. {llmapi_tracker-1.0.6 → llmapi_tracker-1.0.8}/backend/app/routes/pricing.py +0 -0
  29. {llmapi_tracker-1.0.6 → llmapi_tracker-1.0.8}/backend/app/routes/projects.py +0 -0
  30. {llmapi_tracker-1.0.6 → llmapi_tracker-1.0.8}/backend/app/routes/providers.py +0 -0
  31. {llmapi_tracker-1.0.6 → llmapi_tracker-1.0.8}/backend/app/routes/usage.py +0 -0
  32. {llmapi_tracker-1.0.6 → llmapi_tracker-1.0.8}/backend/app/services/__init__.py +0 -0
  33. {llmapi_tracker-1.0.6 → llmapi_tracker-1.0.8}/backend/app/services/cost_service.py +0 -0
  34. {llmapi_tracker-1.0.6 → llmapi_tracker-1.0.8}/backend/app/static/assets/index-3YVy8dav.css +0 -0
  35. {llmapi_tracker-1.0.6 → llmapi_tracker-1.0.8}/llmapi_tracker.egg-info/dependency_links.txt +0 -0
  36. {llmapi_tracker-1.0.6 → llmapi_tracker-1.0.8}/llmapi_tracker.egg-info/entry_points.txt +0 -0
  37. {llmapi_tracker-1.0.6 → llmapi_tracker-1.0.8}/llmapi_tracker.egg-info/requires.txt +0 -0
  38. {llmapi_tracker-1.0.6 → llmapi_tracker-1.0.8}/llmapi_tracker.egg-info/top_level.txt +0 -0
  39. {llmapi_tracker-1.0.6 → llmapi_tracker-1.0.8}/sdk/api_tracker/__init__.py +0 -0
  40. {llmapi_tracker-1.0.6 → llmapi_tracker-1.0.8}/sdk/api_tracker/client.py +0 -0
  41. {llmapi_tracker-1.0.6 → llmapi_tracker-1.0.8}/sdk/api_tracker/models.py +0 -0
  42. {llmapi_tracker-1.0.6 → llmapi_tracker-1.0.8}/sdk/api_tracker/pricing.py +0 -0
  43. {llmapi_tracker-1.0.6 → llmapi_tracker-1.0.8}/sdk/api_tracker/providers/__init__.py +0 -0
  44. {llmapi_tracker-1.0.6 → llmapi_tracker-1.0.8}/sdk/api_tracker/providers/gemini.py +0 -0
  45. {llmapi_tracker-1.0.6 → llmapi_tracker-1.0.8}/sdk/api_tracker/providers/openai.py +0 -0
  46. {llmapi_tracker-1.0.6 → llmapi_tracker-1.0.8}/sdk/api_tracker/py.typed +0 -0
  47. {llmapi_tracker-1.0.6 → llmapi_tracker-1.0.8}/sdk/api_tracker/tracker.py +0 -0
  48. {llmapi_tracker-1.0.6 → llmapi_tracker-1.0.8}/sdk/api_tracker/transport.py +0 -0
  49. {llmapi_tracker-1.0.6 → llmapi_tracker-1.0.8}/setup.cfg +0 -0
  50. {llmapi_tracker-1.0.6 → llmapi_tracker-1.0.8}/tests/test_analytics_routes.py +0 -0
  51. {llmapi_tracker-1.0.6 → llmapi_tracker-1.0.8}/tests/test_cost_service.py +0 -0
  52. {llmapi_tracker-1.0.6 → llmapi_tracker-1.0.8}/tests/test_paths.py +0 -0
  53. {llmapi_tracker-1.0.6 → llmapi_tracker-1.0.8}/tests/test_provider_model_routes.py +0 -0
  54. {llmapi_tracker-1.0.6 → llmapi_tracker-1.0.8}/tests/test_tracker.py +0 -0
  55. {llmapi_tracker-1.0.6 → llmapi_tracker-1.0.8}/tests/test_usage_service.py +0 -0
@@ -0,0 +1,294 @@
1
+ Metadata-Version: 2.4
2
+ Name: llmapi-tracker
3
+ Version: 1.0.8
4
+ Summary: Local-first API usage and cost tracking platform
5
+ Author: API Tracker
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/Lns9777/API-Usage-Tracker
8
+ Project-URL: Repository, https://github.com/Lns9777/API-Usage-Tracker
9
+ Project-URL: Issues, https://github.com/Lns9777/API-Usage-Tracker/issues
10
+ Requires-Python: >=3.10
11
+ Description-Content-Type: text/markdown
12
+ License-File: LICENSE
13
+ Requires-Dist: requests>=2.32
14
+ Requires-Dist: fastapi>=0.115
15
+ Requires-Dist: uvicorn[standard]>=0.30
16
+ Requires-Dist: sqlalchemy>=2.0
17
+ Requires-Dist: pydantic>=2.8
18
+ Provides-Extra: openai
19
+ Requires-Dist: openai>=1.40; extra == "openai"
20
+ Provides-Extra: gemini
21
+ Requires-Dist: google-genai>=0.7; extra == "gemini"
22
+ Provides-Extra: all
23
+ Requires-Dist: openai>=1.40; extra == "all"
24
+ Requires-Dist: google-genai>=0.7; extra == "all"
25
+ Provides-Extra: dev
26
+ Requires-Dist: pytest>=8.0; extra == "dev"
27
+ Requires-Dist: httpx>=0.27; extra == "dev"
28
+ Requires-Dist: ruff>=0.6; extra == "dev"
29
+ Requires-Dist: build>=1.2; extra == "dev"
30
+ Requires-Dist: twine>=5.1; extra == "dev"
31
+ Dynamic: license-file
32
+
33
+ <div align="center">
34
+
35
+ # 📊 llmapi-tracker
36
+
37
+ **A local-first API usage and cost observability platform for LLM applications**
38
+
39
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](#license)
40
+ [![Python](https://img.shields.io/badge/python-3.10%2B-blue.svg)](#prerequisites)
41
+ [![Node](https://img.shields.io/badge/node-20%2B-green.svg)](#prerequisites)
42
+ [![Local First](https://img.shields.io/badge/data-local--first-brightgreen.svg)](#what-api-tracker-is)
43
+
44
+ </div>
45
+
46
+ ---
47
+
48
+ ## Table of Contents
49
+
50
+ - [What API Tracker Is](#what-api-tracker-is)
51
+ - [Features](#features)
52
+ - [Installation](#installation)
53
+ - [Prerequisites](#prerequisites)
54
+ - [Local Development Install](#local-development-install)
55
+ - [Package Install](#package-install)
56
+ - [Starting the Application](#starting-the-application)
57
+ - [SDK Usage](#sdk-usage)
58
+ - [Database Location](#database-location)
59
+ - [Configuration](#configuration)
60
+ - [Development Setup](#development-setup)
61
+ - [Contributing](#contributing)
62
+ - [License](#license)
63
+
64
+ ---
65
+
66
+ ## What API Tracker Is
67
+
68
+ `llmapi-tracker` is a **local-first API usage and cost observability platform** for LLM applications. It records request counts, token usage, latency, errors, and versioned pricing for provider models such as OpenAI and Gemini.
69
+
70
+ The project is designed to keep data local by default. It stores usage and pricing in a local SQLite database and **does not persist prompts, responses, or raw API keys** in the application database.
71
+
72
+ ---
73
+
74
+ ## Features
75
+
76
+ | Category | Capability |
77
+ |---|---|
78
+ | 🖥️ Backend | Local FastAPI backend with SQLite storage |
79
+ | 💰 Pricing | Versioned pricing records for historical cost tracking |
80
+ | 📈 Usage | Capture for input, output, thinking, cached, and total tokens |
81
+ | 🧮 Cost | Request-level and analytics-level cost calculation |
82
+ | 📊 Dashboard | React + Vite dashboard for usage, analytics, providers, models, and pricing |
83
+ | 🔌 SDKs | Wrappers for OpenAI and Gemini |
84
+ | 🚀 Launcher | Packaged launcher command that starts the local app and opens the browser |
85
+ | 🧪 Quality | Open-source repository with tests, CI, and release automation |
86
+
87
+ ---
88
+
89
+ ## Installation
90
+
91
+ ### Prerequisites
92
+
93
+ - Python **3.10** or newer
94
+ - Node.js **20** or newer for frontend development
95
+
96
+ ### Local Development Install
97
+
98
+ ```bash
99
+ python -m venv .venv
100
+ .venv\Scripts\activate
101
+ pip install -r requirements.txt
102
+ cd frontend
103
+ npm install
104
+ ```
105
+
106
+ ### Package Install
107
+
108
+ ```bash
109
+ pip install llmapi-tracker
110
+ ```
111
+
112
+ Optional provider SDK support:
113
+
114
+ ```bash
115
+ pip install "llmapi-tracker[openai]"
116
+ pip install "llmapi-tracker[gemini]"
117
+ pip install "llmapi-tracker[all]"
118
+ ```
119
+
120
+ ---
121
+
122
+ ## Starting the Application
123
+
124
+ ### Backend Only
125
+
126
+ ```bash
127
+ uvicorn backend.app.main:app --reload
128
+ ```
129
+
130
+ ### Frontend Only
131
+
132
+ ```bash
133
+ cd frontend
134
+ npm run dev
135
+ ```
136
+
137
+ ### Packaged App
138
+
139
+ After the frontend assets are built and packaged into the backend static directory:
140
+
141
+ ```bash
142
+ api-tracker
143
+ ```
144
+
145
+ > The launcher starts the backend server and opens the browser to the local dashboard.
146
+
147
+ ---
148
+
149
+ ## SDK Usage
150
+
151
+ ### Initialize the SDK
152
+
153
+ ```python
154
+ import os
155
+ from api_tracker import APITracker
156
+
157
+ tracker = APITracker(
158
+ project="Pharmacy-AI",
159
+ backend_url=os.getenv("TRACKER_BACKEND_URL", "http://localhost:8000"),
160
+ openai_api_key=os.getenv("OPENAI_API_KEY"),
161
+ gemini_api_key=os.getenv("GEMINI_API_KEY"),
162
+ )
163
+ ```
164
+
165
+ ### OpenAI
166
+
167
+ ```python
168
+ response = tracker.openai.chat(
169
+ model="gpt-5",
170
+ messages=[{"role": "user", "content": "Analyze this prescription"}],
171
+ )
172
+ ```
173
+
174
+ ### Gemini
175
+
176
+ ```python
177
+ response = tracker.gemini.generate(
178
+ model="gemini-2.5-flash",
179
+ contents="Analyze this prescription",
180
+ )
181
+ ```
182
+
183
+ > The SDK reads usage metadata from the provider response and sends it to the local backend automatically.
184
+
185
+ ---
186
+
187
+ ## Database Location
188
+
189
+ By default, the SQLite database is stored in a stable user data directory so it survives uninstall and reinstall.
190
+
191
+ The path is resolved in this order:
192
+
193
+ 1. `API_TRACKER_DB_PATH` if set
194
+ 2. `DATABASE_URL` if set
195
+ 3. A user-data location based on the operating system
196
+
197
+ **Typical defaults**
198
+
199
+ | OS | Path |
200
+ |---|---|
201
+ | Windows | `%LOCALAPPDATA%\APITracker\api_tracker.db` |
202
+ | macOS / Linux | `~/.local/share/APITracker/api_tracker.db` |
203
+
204
+ > If the preferred user-data directory cannot be created, the app falls back to a writable local `.api-tracker-data/` directory in the current workspace.
205
+
206
+ ---
207
+
208
+ ## Configuration
209
+
210
+ Environment variables:
211
+
212
+ | Variable | Description |
213
+ |---|---|
214
+ | `DATABASE_URL` | Overrides the database connection string |
215
+ | `API_TRACKER_DATA_DIR` | Sets the user-data root used for SQLite storage |
216
+ | `API_TRACKER_DB_PATH` | Points directly to a specific SQLite file |
217
+ | `API_TRACKER_HOST` | Host the backend binds to (default `127.0.0.1`) |
218
+ | `API_TRACKER_PORT` | Port the backend binds to (default `8000`) |
219
+ | `API_TRACKER_OPEN_BROWSER` | Set to `false` to disable browser auto-open for the launcher |
220
+ | `OPENAI_API_KEY` | API key used by the OpenAI SDK wrapper |
221
+ | `GEMINI_API_KEY` | API key used by the Gemini SDK wrapper |
222
+ | `TRACKER_BACKEND_URL` | Backend URL used by the SDK client |
223
+ | `VITE_API_BASE_URL` | Backend URL used by the frontend dashboard |
224
+
225
+ Example `.env`:
226
+
227
+ ```bash
228
+ DATABASE_URL=sqlite:///./api_tracker.db
229
+ API_TRACKER_DATA_DIR=
230
+ API_TRACKER_DB_PATH=
231
+ API_TRACKER_HOST=127.0.0.1
232
+ API_TRACKER_PORT=8000
233
+ API_TRACKER_OPEN_BROWSER=true
234
+ OPENAI_API_KEY=
235
+ GEMINI_API_KEY=
236
+ TRACKER_BACKEND_URL=http://localhost:8000
237
+ VITE_API_BASE_URL=http://localhost:8000
238
+ ```
239
+
240
+ ---
241
+
242
+ ## Development Setup
243
+
244
+ ### Backend
245
+
246
+ ```bash
247
+ python -m compileall backend sdk tests
248
+ pytest -q
249
+ ```
250
+
251
+ ### Frontend
252
+
253
+ ```bash
254
+ cd frontend
255
+ npm run build
256
+ ```
257
+
258
+ ### Packaging Assets
259
+
260
+ ```bash
261
+ cd frontend
262
+ npm run build
263
+ cd ..
264
+ python scripts/package_frontend_assets.py
265
+ python scripts/verify_frontend_assets.py
266
+ ```
267
+
268
+ ---
269
+
270
+ ## Contributing
271
+
272
+ `llmapi-tracker` is intended to be open source and contributor friendly.
273
+
274
+ **Contribution files**
275
+
276
+ - [`CONTRIBUTING.md`](L:/Personal/APITracker/CONTRIBUTING.md)
277
+ - [`CODE_OF_CONDUCT.md`](L:/Personal/APITracker/CODE_OF_CONDUCT.md)
278
+ - [`SECURITY.md`](L:/Personal/APITracker/SECURITY.md)
279
+ - [`CHANGELOG.md`](L:/Personal/APITracker/CHANGELOG.md)
280
+ - [`LICENSE`](L:/Personal/APITracker/LICENSE)
281
+
282
+ **Contribution expectations**
283
+
284
+ - ✅ Open changes through pull requests
285
+ - ✅ Require review before merge
286
+ - ✅ Keep the app local-first
287
+ - 🚫 Avoid storing prompts, responses, or raw API keys
288
+ - ✅ Add tests for behavior changes
289
+
290
+ ---
291
+
292
+ ## License
293
+
294
+ `llmapi-tracker` is released under the **MIT License**. See [`LICENSE`](L:/Personal/APITracker/LICENSE).
@@ -0,0 +1,262 @@
1
+ <div align="center">
2
+
3
+ # 📊 llmapi-tracker
4
+
5
+ **A local-first API usage and cost observability platform for LLM applications**
6
+
7
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](#license)
8
+ [![Python](https://img.shields.io/badge/python-3.10%2B-blue.svg)](#prerequisites)
9
+ [![Node](https://img.shields.io/badge/node-20%2B-green.svg)](#prerequisites)
10
+ [![Local First](https://img.shields.io/badge/data-local--first-brightgreen.svg)](#what-api-tracker-is)
11
+
12
+ </div>
13
+
14
+ ---
15
+
16
+ ## Table of Contents
17
+
18
+ - [What API Tracker Is](#what-api-tracker-is)
19
+ - [Features](#features)
20
+ - [Installation](#installation)
21
+ - [Prerequisites](#prerequisites)
22
+ - [Local Development Install](#local-development-install)
23
+ - [Package Install](#package-install)
24
+ - [Starting the Application](#starting-the-application)
25
+ - [SDK Usage](#sdk-usage)
26
+ - [Database Location](#database-location)
27
+ - [Configuration](#configuration)
28
+ - [Development Setup](#development-setup)
29
+ - [Contributing](#contributing)
30
+ - [License](#license)
31
+
32
+ ---
33
+
34
+ ## What API Tracker Is
35
+
36
+ `llmapi-tracker` is a **local-first API usage and cost observability platform** for LLM applications. It records request counts, token usage, latency, errors, and versioned pricing for provider models such as OpenAI and Gemini.
37
+
38
+ The project is designed to keep data local by default. It stores usage and pricing in a local SQLite database and **does not persist prompts, responses, or raw API keys** in the application database.
39
+
40
+ ---
41
+
42
+ ## Features
43
+
44
+ | Category | Capability |
45
+ |---|---|
46
+ | 🖥️ Backend | Local FastAPI backend with SQLite storage |
47
+ | 💰 Pricing | Versioned pricing records for historical cost tracking |
48
+ | 📈 Usage | Capture for input, output, thinking, cached, and total tokens |
49
+ | 🧮 Cost | Request-level and analytics-level cost calculation |
50
+ | 📊 Dashboard | React + Vite dashboard for usage, analytics, providers, models, and pricing |
51
+ | 🔌 SDKs | Wrappers for OpenAI and Gemini |
52
+ | 🚀 Launcher | Packaged launcher command that starts the local app and opens the browser |
53
+ | 🧪 Quality | Open-source repository with tests, CI, and release automation |
54
+
55
+ ---
56
+
57
+ ## Installation
58
+
59
+ ### Prerequisites
60
+
61
+ - Python **3.10** or newer
62
+ - Node.js **20** or newer for frontend development
63
+
64
+ ### Local Development Install
65
+
66
+ ```bash
67
+ python -m venv .venv
68
+ .venv\Scripts\activate
69
+ pip install -r requirements.txt
70
+ cd frontend
71
+ npm install
72
+ ```
73
+
74
+ ### Package Install
75
+
76
+ ```bash
77
+ pip install llmapi-tracker
78
+ ```
79
+
80
+ Optional provider SDK support:
81
+
82
+ ```bash
83
+ pip install "llmapi-tracker[openai]"
84
+ pip install "llmapi-tracker[gemini]"
85
+ pip install "llmapi-tracker[all]"
86
+ ```
87
+
88
+ ---
89
+
90
+ ## Starting the Application
91
+
92
+ ### Backend Only
93
+
94
+ ```bash
95
+ uvicorn backend.app.main:app --reload
96
+ ```
97
+
98
+ ### Frontend Only
99
+
100
+ ```bash
101
+ cd frontend
102
+ npm run dev
103
+ ```
104
+
105
+ ### Packaged App
106
+
107
+ After the frontend assets are built and packaged into the backend static directory:
108
+
109
+ ```bash
110
+ api-tracker
111
+ ```
112
+
113
+ > The launcher starts the backend server and opens the browser to the local dashboard.
114
+
115
+ ---
116
+
117
+ ## SDK Usage
118
+
119
+ ### Initialize the SDK
120
+
121
+ ```python
122
+ import os
123
+ from api_tracker import APITracker
124
+
125
+ tracker = APITracker(
126
+ project="Pharmacy-AI",
127
+ backend_url=os.getenv("TRACKER_BACKEND_URL", "http://localhost:8000"),
128
+ openai_api_key=os.getenv("OPENAI_API_KEY"),
129
+ gemini_api_key=os.getenv("GEMINI_API_KEY"),
130
+ )
131
+ ```
132
+
133
+ ### OpenAI
134
+
135
+ ```python
136
+ response = tracker.openai.chat(
137
+ model="gpt-5",
138
+ messages=[{"role": "user", "content": "Analyze this prescription"}],
139
+ )
140
+ ```
141
+
142
+ ### Gemini
143
+
144
+ ```python
145
+ response = tracker.gemini.generate(
146
+ model="gemini-2.5-flash",
147
+ contents="Analyze this prescription",
148
+ )
149
+ ```
150
+
151
+ > The SDK reads usage metadata from the provider response and sends it to the local backend automatically.
152
+
153
+ ---
154
+
155
+ ## Database Location
156
+
157
+ By default, the SQLite database is stored in a stable user data directory so it survives uninstall and reinstall.
158
+
159
+ The path is resolved in this order:
160
+
161
+ 1. `API_TRACKER_DB_PATH` if set
162
+ 2. `DATABASE_URL` if set
163
+ 3. A user-data location based on the operating system
164
+
165
+ **Typical defaults**
166
+
167
+ | OS | Path |
168
+ |---|---|
169
+ | Windows | `%LOCALAPPDATA%\APITracker\api_tracker.db` |
170
+ | macOS / Linux | `~/.local/share/APITracker/api_tracker.db` |
171
+
172
+ > If the preferred user-data directory cannot be created, the app falls back to a writable local `.api-tracker-data/` directory in the current workspace.
173
+
174
+ ---
175
+
176
+ ## Configuration
177
+
178
+ Environment variables:
179
+
180
+ | Variable | Description |
181
+ |---|---|
182
+ | `DATABASE_URL` | Overrides the database connection string |
183
+ | `API_TRACKER_DATA_DIR` | Sets the user-data root used for SQLite storage |
184
+ | `API_TRACKER_DB_PATH` | Points directly to a specific SQLite file |
185
+ | `API_TRACKER_HOST` | Host the backend binds to (default `127.0.0.1`) |
186
+ | `API_TRACKER_PORT` | Port the backend binds to (default `8000`) |
187
+ | `API_TRACKER_OPEN_BROWSER` | Set to `false` to disable browser auto-open for the launcher |
188
+ | `OPENAI_API_KEY` | API key used by the OpenAI SDK wrapper |
189
+ | `GEMINI_API_KEY` | API key used by the Gemini SDK wrapper |
190
+ | `TRACKER_BACKEND_URL` | Backend URL used by the SDK client |
191
+ | `VITE_API_BASE_URL` | Backend URL used by the frontend dashboard |
192
+
193
+ Example `.env`:
194
+
195
+ ```bash
196
+ DATABASE_URL=sqlite:///./api_tracker.db
197
+ API_TRACKER_DATA_DIR=
198
+ API_TRACKER_DB_PATH=
199
+ API_TRACKER_HOST=127.0.0.1
200
+ API_TRACKER_PORT=8000
201
+ API_TRACKER_OPEN_BROWSER=true
202
+ OPENAI_API_KEY=
203
+ GEMINI_API_KEY=
204
+ TRACKER_BACKEND_URL=http://localhost:8000
205
+ VITE_API_BASE_URL=http://localhost:8000
206
+ ```
207
+
208
+ ---
209
+
210
+ ## Development Setup
211
+
212
+ ### Backend
213
+
214
+ ```bash
215
+ python -m compileall backend sdk tests
216
+ pytest -q
217
+ ```
218
+
219
+ ### Frontend
220
+
221
+ ```bash
222
+ cd frontend
223
+ npm run build
224
+ ```
225
+
226
+ ### Packaging Assets
227
+
228
+ ```bash
229
+ cd frontend
230
+ npm run build
231
+ cd ..
232
+ python scripts/package_frontend_assets.py
233
+ python scripts/verify_frontend_assets.py
234
+ ```
235
+
236
+ ---
237
+
238
+ ## Contributing
239
+
240
+ `llmapi-tracker` is intended to be open source and contributor friendly.
241
+
242
+ **Contribution files**
243
+
244
+ - [`CONTRIBUTING.md`](L:/Personal/APITracker/CONTRIBUTING.md)
245
+ - [`CODE_OF_CONDUCT.md`](L:/Personal/APITracker/CODE_OF_CONDUCT.md)
246
+ - [`SECURITY.md`](L:/Personal/APITracker/SECURITY.md)
247
+ - [`CHANGELOG.md`](L:/Personal/APITracker/CHANGELOG.md)
248
+ - [`LICENSE`](L:/Personal/APITracker/LICENSE)
249
+
250
+ **Contribution expectations**
251
+
252
+ - ✅ Open changes through pull requests
253
+ - ✅ Require review before merge
254
+ - ✅ Keep the app local-first
255
+ - 🚫 Avoid storing prompts, responses, or raw API keys
256
+ - ✅ Add tests for behavior changes
257
+
258
+ ---
259
+
260
+ ## License
261
+
262
+ `llmapi-tracker` is released under the **MIT License**. See [`LICENSE`](L:/Personal/APITracker/LICENSE).
@@ -0,0 +1,44 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ import socket
5
+ import threading
6
+ import webbrowser
7
+
8
+ import uvicorn
9
+
10
+
11
+ def find_available_port(host: str, preferred_port: int = 8000) -> int:
12
+ """Prefer the standard port, then choose the first available port."""
13
+ candidates = (
14
+ [preferred_port]
15
+ if preferred_port == 0
16
+ else range(preferred_port, preferred_port + 100)
17
+ )
18
+ for port in candidates:
19
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as probe:
20
+ probe.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
21
+ try:
22
+ probe.bind((host, port))
23
+ except OSError:
24
+ continue
25
+ return probe.getsockname()[1]
26
+ raise RuntimeError(
27
+ f"No available port found between {preferred_port} and {preferred_port + 99}"
28
+ )
29
+
30
+
31
+ def launch_app(*_args: str) -> None:
32
+ host = os.getenv("API_TRACKER_HOST", "127.0.0.1")
33
+ requested_port = int(os.getenv("API_TRACKER_PORT", "8000"))
34
+ port = find_available_port(host, requested_port)
35
+ open_browser = os.getenv("API_TRACKER_OPEN_BROWSER", "true").lower() not in {
36
+ "0",
37
+ "false",
38
+ "no",
39
+ }
40
+ print(f"API Tracker dashboard: http://{host}:{port}")
41
+ if open_browser:
42
+ url = f"http://{host}:{port}"
43
+ threading.Timer(1.0, lambda: webbrowser.open(url)).start()
44
+ uvicorn.run("backend.app.main:app", host=host, port=port, reload=False)
@@ -1,9 +1,9 @@
1
1
  from __future__ import annotations
2
2
 
3
- from datetime import datetime
3
+ from datetime import datetime, timezone
4
4
  from typing import Any
5
5
 
6
- from pydantic import BaseModel, ConfigDict, Field
6
+ from pydantic import BaseModel, ConfigDict, Field, field_serializer
7
7
 
8
8
 
9
9
  class ProjectBase(BaseModel):
@@ -84,6 +84,8 @@ class UsageDataIn(BaseModel):
84
84
  model: str
85
85
  internal_request_id: str
86
86
  provider_request_id: str | None = None
87
+ # Timestamp of the provider request. If omitted, the ingestion time is used.
88
+ timestamp: datetime | None = None
87
89
  input_tokens: int = 0
88
90
  output_tokens: int = 0
89
91
  thinking_tokens: int = 0
@@ -128,3 +130,10 @@ class UsageResponse(BaseModel):
128
130
  metadata_json: dict[str, Any]
129
131
  capture_content: bool
130
132
  model_config = ConfigDict(from_attributes=True)
133
+
134
+ @field_serializer("timestamp")
135
+ def serialize_timestamp(self, value: datetime) -> str:
136
+ """Always expose stored timestamps as explicit UTC ISO-8601 values."""
137
+ if value.tzinfo is None:
138
+ value = value.replace(tzinfo=timezone.utc)
139
+ return value.astimezone(timezone.utc).isoformat().replace("+00:00", "Z")
@@ -76,8 +76,13 @@ def create_usage(db: Session, payload: dict[str, Any]) -> ApiUsage:
76
76
  project = _get_or_create_project(db, payload["project"])
77
77
  provider = _get_or_create_provider(db, payload["provider"])
78
78
  model = _get_or_create_model(db, provider.id, payload["model"])
79
- # timestamp = payload.get("timestamp") or datetime.utcnow()
80
79
  timestamp = payload.get("timestamp") or datetime.now(timezone.utc)
80
+ # SQLite does not preserve timezone metadata. Normalize incoming values
81
+ # before using them for pricing and persistence so all records mean UTC.
82
+ if timestamp.tzinfo is None:
83
+ timestamp = timestamp.replace(tzinfo=timezone.utc)
84
+ else:
85
+ timestamp = timestamp.astimezone(timezone.utc)
81
86
  pricing = get_applicable_pricing(db, model.id, timestamp)
82
87
  snapshot = PricingSnapshot(
83
88
  input_price_per_1m=getattr(pricing, "input_price_per_1m", 0.0),