apidepth 0.1.0__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.
- apidepth-0.1.0/.github/workflows/ci.yml +41 -0
- apidepth-0.1.0/.gitignore +38 -0
- apidepth-0.1.0/LICENSE +21 -0
- apidepth-0.1.0/PKG-INFO +282 -0
- apidepth-0.1.0/README.md +213 -0
- apidepth-0.1.0/apidepth/__init__.py +220 -0
- apidepth-0.1.0/apidepth/collector.py +606 -0
- apidepth-0.1.0/apidepth/configuration.py +95 -0
- apidepth-0.1.0/apidepth/event.py +60 -0
- apidepth-0.1.0/apidepth/instrumentation.py +442 -0
- apidepth-0.1.0/apidepth/integrations/__init__.py +0 -0
- apidepth-0.1.0/apidepth/integrations/django.py +106 -0
- apidepth-0.1.0/apidepth/integrations/flask.py +118 -0
- apidepth-0.1.0/apidepth/py.typed +0 -0
- apidepth-0.1.0/apidepth/rate_limit_headers.py +245 -0
- apidepth-0.1.0/apidepth/registry_loader.py +448 -0
- apidepth-0.1.0/apidepth/vendor_registry.py +319 -0
- apidepth-0.1.0/apidepth/version.py +9 -0
- apidepth-0.1.0/examples/setup.py +48 -0
- apidepth-0.1.0/pyproject.toml +69 -0
- apidepth-0.1.0/tests/test_ssrf.py +51 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
name: Lint and test
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
|
|
14
|
+
strategy:
|
|
15
|
+
matrix:
|
|
16
|
+
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
|
|
17
|
+
|
|
18
|
+
steps:
|
|
19
|
+
- uses: actions/checkout@v4
|
|
20
|
+
|
|
21
|
+
- name: Checkout collector (SSRF fixture)
|
|
22
|
+
uses: actions/checkout@v4
|
|
23
|
+
with:
|
|
24
|
+
repository: cmwright33/apidepth-collector
|
|
25
|
+
token: ${{ secrets.GH_PAT }}
|
|
26
|
+
path: apidepth-collector
|
|
27
|
+
sparse-checkout: tests/fixtures
|
|
28
|
+
sparse-checkout-cone-mode: false
|
|
29
|
+
|
|
30
|
+
- uses: actions/setup-python@v5
|
|
31
|
+
with:
|
|
32
|
+
python-version: ${{ matrix.python-version }}
|
|
33
|
+
|
|
34
|
+
- name: Install dev dependencies
|
|
35
|
+
run: pip install -e ".[dev]"
|
|
36
|
+
|
|
37
|
+
- name: Lint
|
|
38
|
+
run: ruff check .
|
|
39
|
+
|
|
40
|
+
- name: Tests
|
|
41
|
+
run: pytest
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.pyo
|
|
5
|
+
*.pyd
|
|
6
|
+
.Python
|
|
7
|
+
*.egg
|
|
8
|
+
*.egg-info/
|
|
9
|
+
dist/
|
|
10
|
+
build/
|
|
11
|
+
.eggs/
|
|
12
|
+
*.whl
|
|
13
|
+
|
|
14
|
+
# Virtual environments
|
|
15
|
+
.venv/
|
|
16
|
+
venv/
|
|
17
|
+
env/
|
|
18
|
+
.env
|
|
19
|
+
|
|
20
|
+
# Testing
|
|
21
|
+
.pytest_cache/
|
|
22
|
+
.coverage
|
|
23
|
+
htmlcov/
|
|
24
|
+
.tox/
|
|
25
|
+
|
|
26
|
+
# Type checking
|
|
27
|
+
.mypy_cache/
|
|
28
|
+
.ruff_cache/
|
|
29
|
+
|
|
30
|
+
# Editor
|
|
31
|
+
.DS_Store
|
|
32
|
+
.idea/
|
|
33
|
+
.vscode/
|
|
34
|
+
*.swp
|
|
35
|
+
*.swo
|
|
36
|
+
|
|
37
|
+
# Runtime
|
|
38
|
+
/tmp/apidepth_registry.json
|
apidepth-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Apidepth
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
apidepth-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: apidepth
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Apidepth SDK for Python — track outbound API latency, error rates, and rate limit quota.
|
|
5
|
+
Project-URL: Homepage, https://www.apidepth.io
|
|
6
|
+
Project-URL: Source, https://github.com/apidepth/apidepth-python
|
|
7
|
+
Project-URL: Issues, https://github.com/apidepth/apidepth-python/issues
|
|
8
|
+
License: MIT License
|
|
9
|
+
|
|
10
|
+
Copyright (c) 2026 Apidepth
|
|
11
|
+
|
|
12
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
13
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
14
|
+
in the Software without restriction, including without limitation the rights
|
|
15
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
16
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
17
|
+
furnished to do so, subject to the following conditions:
|
|
18
|
+
|
|
19
|
+
The above copyright notice and this permission notice shall be included in all
|
|
20
|
+
copies or substantial portions of the Software.
|
|
21
|
+
|
|
22
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
23
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
24
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
25
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
26
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
27
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
28
|
+
SOFTWARE.
|
|
29
|
+
License-File: LICENSE
|
|
30
|
+
Keywords: api,apidepth,http,monitoring,observability
|
|
31
|
+
Classifier: Development Status :: 4 - Beta
|
|
32
|
+
Classifier: Framework :: Django
|
|
33
|
+
Classifier: Framework :: Flask
|
|
34
|
+
Classifier: Intended Audience :: Developers
|
|
35
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
36
|
+
Classifier: Operating System :: OS Independent
|
|
37
|
+
Classifier: Programming Language :: Python :: 3
|
|
38
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
39
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
40
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
41
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
42
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
43
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
44
|
+
Classifier: Topic :: System :: Monitoring
|
|
45
|
+
Classifier: Typing :: Typed
|
|
46
|
+
Requires-Python: >=3.9
|
|
47
|
+
Provides-Extra: all
|
|
48
|
+
Requires-Dist: django>=3.2; extra == 'all'
|
|
49
|
+
Requires-Dist: flask>=2.0; extra == 'all'
|
|
50
|
+
Requires-Dist: httpx>=0.23; extra == 'all'
|
|
51
|
+
Requires-Dist: requests>=2.20; extra == 'all'
|
|
52
|
+
Provides-Extra: dev
|
|
53
|
+
Requires-Dist: httpx>=0.23; extra == 'dev'
|
|
54
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
55
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
56
|
+
Requires-Dist: requests>=2.20; extra == 'dev'
|
|
57
|
+
Requires-Dist: responses>=0.25; extra == 'dev'
|
|
58
|
+
Requires-Dist: respx>=0.21; extra == 'dev'
|
|
59
|
+
Requires-Dist: ruff>=0.4; extra == 'dev'
|
|
60
|
+
Provides-Extra: django
|
|
61
|
+
Requires-Dist: django>=3.2; extra == 'django'
|
|
62
|
+
Provides-Extra: flask
|
|
63
|
+
Requires-Dist: flask>=2.0; extra == 'flask'
|
|
64
|
+
Provides-Extra: httpx
|
|
65
|
+
Requires-Dist: httpx>=0.23; extra == 'httpx'
|
|
66
|
+
Provides-Extra: requests
|
|
67
|
+
Requires-Dist: requests>=2.20; extra == 'requests'
|
|
68
|
+
Description-Content-Type: text/markdown
|
|
69
|
+
|
|
70
|
+
# apidepth-python
|
|
71
|
+
|
|
72
|
+
Track outbound API latency, error rates, and rate limit quota across your third-party vendors — Stripe, OpenAI, Anthropic, Twilio, GitHub, and more.
|
|
73
|
+
|
|
74
|
+
Zero config for supported vendors. No code changes to your existing HTTP calls.
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## Installation
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
pip install apidepth
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
For `requests` instrumentation (most common):
|
|
85
|
+
```bash
|
|
86
|
+
pip install "apidepth[requests]"
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
For `httpx` instrumentation:
|
|
90
|
+
```bash
|
|
91
|
+
pip install "apidepth[httpx]"
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## Quick start
|
|
97
|
+
|
|
98
|
+
### Django
|
|
99
|
+
|
|
100
|
+
Add to `INSTALLED_APPS` and configure in `settings.py`:
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
INSTALLED_APPS = [
|
|
104
|
+
...
|
|
105
|
+
"apidepth.integrations.django",
|
|
106
|
+
]
|
|
107
|
+
|
|
108
|
+
APIDEPTH = {
|
|
109
|
+
"api_key": env("APIDEPTH_API_KEY"),
|
|
110
|
+
"environment": env("DJANGO_ENV", default="development"),
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Flask
|
|
115
|
+
|
|
116
|
+
```python
|
|
117
|
+
from flask import Flask
|
|
118
|
+
from apidepth.integrations.flask import Apidepth
|
|
119
|
+
|
|
120
|
+
app = Flask(__name__)
|
|
121
|
+
app.config["APIDEPTH_API_KEY"] = os.environ["APIDEPTH_API_KEY"]
|
|
122
|
+
app.config["APIDEPTH_ENVIRONMENT"] = "production"
|
|
123
|
+
|
|
124
|
+
Apidepth(app)
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Standalone / scripts
|
|
128
|
+
|
|
129
|
+
```python
|
|
130
|
+
import apidepth
|
|
131
|
+
from apidepth import registry_loader
|
|
132
|
+
|
|
133
|
+
apidepth.configure(
|
|
134
|
+
api_key=os.environ["APIDEPTH_API_KEY"],
|
|
135
|
+
environment="production",
|
|
136
|
+
)
|
|
137
|
+
apidepth.instrument() # call before any outbound HTTP
|
|
138
|
+
registry_loader.load_and_start() # loads remote vendor registry + starts refresh thread
|
|
139
|
+
|
|
140
|
+
import requests
|
|
141
|
+
resp = requests.get("https://api.stripe.com/v1/charges/ch_abc123", ...)
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
`load_and_start()` fetches the latest vendor registry from the network (with a local disk cache fallback) and starts a background refresh thread. Without it, only the six bundled vendors are recognised. Django and Flask integrations call this automatically.
|
|
145
|
+
|
|
146
|
+
For **Gunicorn / uWSGI**, call `Collector.register_fork_safety()` once before the server forks so each worker gets its own flush thread:
|
|
147
|
+
|
|
148
|
+
```python
|
|
149
|
+
from apidepth.collector import Collector
|
|
150
|
+
Collector.register_fork_safety()
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## Configuration
|
|
156
|
+
|
|
157
|
+
| Option | Default | Description |
|
|
158
|
+
|---|---|---|
|
|
159
|
+
| `api_key` | `None` | **Required.** Your Apidepth API key. |
|
|
160
|
+
| `environment` | `None` | Deployment environment tag, e.g. `"production"`. |
|
|
161
|
+
| `enabled` | `True` | Set `False` to disable all instrumentation. |
|
|
162
|
+
| `sample_rate` | `1.0` | Float 0.0–1.0. Fraction of requests to capture. |
|
|
163
|
+
| `ignored_hosts` | `[]` | List of hostnames to never record. |
|
|
164
|
+
| `extra_vendors` | `{}` | Map `{"vendor-name": "host"}` for in-house APIs. |
|
|
165
|
+
| `flush_interval` | `20` | Background flush interval in seconds. |
|
|
166
|
+
| `registry_cache_path` | `/tmp/apidepth_registry.json` | Disk cache for the vendor registry. |
|
|
167
|
+
| `registry_refresh_interval` | `21600` | Registry refresh interval in seconds (6 h). |
|
|
168
|
+
| `on_flush_error` | `None` | `Callable(exc, ctx)` for routing errors to Sentry etc. |
|
|
169
|
+
| `collector_url` | production endpoint | Override for self-hosted collectors. |
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## What gets captured
|
|
174
|
+
|
|
175
|
+
Every outbound HTTP request to a recognised vendor produces one event:
|
|
176
|
+
|
|
177
|
+
| Field | Example |
|
|
178
|
+
|---|---|
|
|
179
|
+
| `vendor` | `"stripe"` |
|
|
180
|
+
| `endpoint` | `"/v1/charges/:id"` |
|
|
181
|
+
| `method` | `"POST"` |
|
|
182
|
+
| `status` | `200` |
|
|
183
|
+
| `outcome` | `"success"` / `"client_error"` / `"server_error"` / `"timeout"` |
|
|
184
|
+
| `duration_ms` | `234` |
|
|
185
|
+
| `cold_start` | `false` (always — see [Known differences](#known-differences-from-the-ruby-gem)) |
|
|
186
|
+
| `env` | `"production"` |
|
|
187
|
+
| `ts` | `1747008000000` (epoch ms) |
|
|
188
|
+
| `rl_remaining` | `4999` (when rate limit headers present) |
|
|
189
|
+
| `rl_limit` | `5000` |
|
|
190
|
+
| `rl_reset_at` | `1747008060000` (epoch ms) |
|
|
191
|
+
|
|
192
|
+
**Never captured:** request/response bodies, headers, query parameters, credentials.
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
## Supported vendors
|
|
197
|
+
|
|
198
|
+
| Vendor | Host |
|
|
199
|
+
|---|---|
|
|
200
|
+
| Stripe | `api.stripe.com` |
|
|
201
|
+
| OpenAI | `api.openai.com` |
|
|
202
|
+
| Anthropic | `api.anthropic.com` |
|
|
203
|
+
| Twilio | `api.twilio.com` |
|
|
204
|
+
| Resend | `api.resend.com` |
|
|
205
|
+
| GitHub | `api.github.com` |
|
|
206
|
+
|
|
207
|
+
Additional vendors are loaded from the remote registry every 6 hours.
|
|
208
|
+
|
|
209
|
+
### Custom vendors
|
|
210
|
+
|
|
211
|
+
```python
|
|
212
|
+
apidepth.configure(
|
|
213
|
+
extra_vendors={"payments-api": "api.payments.internal"},
|
|
214
|
+
)
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
---
|
|
218
|
+
|
|
219
|
+
## Rate limit tracking
|
|
220
|
+
|
|
221
|
+
The SDK extracts quota state from response headers and includes it in every event.
|
|
222
|
+
Supported header families (checked in priority order):
|
|
223
|
+
|
|
224
|
+
- **OpenAI / Anthropic**: `x-ratelimit-remaining-requests`, `x-ratelimit-limit-requests`, `x-ratelimit-reset-requests`
|
|
225
|
+
- **GitHub**: `x-ratelimit-remaining`, `x-ratelimit-limit`, `x-ratelimit-reset`
|
|
226
|
+
- **IETF draft / HubSpot**: `ratelimit-remaining`, `ratelimit-limit`, `ratelimit-reset`
|
|
227
|
+
- **Stripe / generic 429**: `retry-after`
|
|
228
|
+
|
|
229
|
+
Reset values are normalised to epoch milliseconds regardless of the source format (Unix timestamp, seconds-from-now, OpenAI duration strings like `"1m30s"`).
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
## Debugging
|
|
234
|
+
|
|
235
|
+
```python
|
|
236
|
+
from apidepth.collector import Collector
|
|
237
|
+
|
|
238
|
+
print(Collector.instance().stats())
|
|
239
|
+
# {
|
|
240
|
+
# 'queue_size': 0,
|
|
241
|
+
# 'consecutive_failures': 0,
|
|
242
|
+
# 'total_dropped': 0,
|
|
243
|
+
# 'last_flush_at': 1747008000.123,
|
|
244
|
+
# }
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
---
|
|
248
|
+
|
|
249
|
+
## Framework compatibility
|
|
250
|
+
|
|
251
|
+
| Framework | Version | Integration |
|
|
252
|
+
|---|---|---|
|
|
253
|
+
| Django | 3.2+ | `apidepth.integrations.django` in `INSTALLED_APPS` |
|
|
254
|
+
| Flask | 2.0+ | `Apidepth(app)` |
|
|
255
|
+
| FastAPI / Starlette | any | Call `apidepth.instrument()` at startup |
|
|
256
|
+
| Scripts / workers | — | Call `apidepth.instrument()` at startup |
|
|
257
|
+
|
|
258
|
+
---
|
|
259
|
+
|
|
260
|
+
## Python compatibility
|
|
261
|
+
|
|
262
|
+
Python 3.9–3.13. No required runtime dependencies (stdlib only). `requests` and `httpx` are optional instrumentation targets detected at runtime.
|
|
263
|
+
|
|
264
|
+
---
|
|
265
|
+
|
|
266
|
+
## Known differences from the Ruby gem
|
|
267
|
+
|
|
268
|
+
### `cold_start` is always `false`
|
|
269
|
+
|
|
270
|
+
The Ruby gem tags the **first** outbound request on each TCP connection with `cold_start: true` using `Net::HTTP#started?`. The Apidepth collector uses this flag to exclude DNS + TCP + TLS handshake overhead from latency percentile calculations (p50/p95/p99), keeping those metrics representative of steady-state vendor performance.
|
|
271
|
+
|
|
272
|
+
Neither `requests` (backed by urllib3) nor `httpx` exposes a public API for detecting whether the underlying socket is a keep-alive reuse. The Python SDK therefore always sends `cold_start: false`.
|
|
273
|
+
|
|
274
|
+
**Practical impact depends on your traffic pattern:**
|
|
275
|
+
|
|
276
|
+
| Traffic pattern | Impact |
|
|
277
|
+
|---|---|
|
|
278
|
+
| High-throughput web service | **Negligible** — cold starts are a tiny fraction of total requests; percentile inflation is unmeasurable |
|
|
279
|
+
| Low-throughput service / cron job | **Noticeable** — the first request per run pays ~50–200 ms of connection overhead that isn't excluded from percentiles; p95/p99 may read slightly higher than in Ruby |
|
|
280
|
+
| Serverless / short-lived worker | **Material** — every invocation starts cold; all latency data includes connection overhead; comparisons against Ruby-instrumented services will show the Python side as systematically higher |
|
|
281
|
+
|
|
282
|
+
The raw duration values are accurate — only the percentile statistics are affected. If cold-start exclusion matters for your environment, filter those events manually in your dashboard (e.g. a warm-up request flag in thread-local state) until the underlying libraries expose the required connection-state API.
|
apidepth-0.1.0/README.md
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
# apidepth-python
|
|
2
|
+
|
|
3
|
+
Track outbound API latency, error rates, and rate limit quota across your third-party vendors — Stripe, OpenAI, Anthropic, Twilio, GitHub, and more.
|
|
4
|
+
|
|
5
|
+
Zero config for supported vendors. No code changes to your existing HTTP calls.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pip install apidepth
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
For `requests` instrumentation (most common):
|
|
16
|
+
```bash
|
|
17
|
+
pip install "apidepth[requests]"
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
For `httpx` instrumentation:
|
|
21
|
+
```bash
|
|
22
|
+
pip install "apidepth[httpx]"
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Quick start
|
|
28
|
+
|
|
29
|
+
### Django
|
|
30
|
+
|
|
31
|
+
Add to `INSTALLED_APPS` and configure in `settings.py`:
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
INSTALLED_APPS = [
|
|
35
|
+
...
|
|
36
|
+
"apidepth.integrations.django",
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
APIDEPTH = {
|
|
40
|
+
"api_key": env("APIDEPTH_API_KEY"),
|
|
41
|
+
"environment": env("DJANGO_ENV", default="development"),
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Flask
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
from flask import Flask
|
|
49
|
+
from apidepth.integrations.flask import Apidepth
|
|
50
|
+
|
|
51
|
+
app = Flask(__name__)
|
|
52
|
+
app.config["APIDEPTH_API_KEY"] = os.environ["APIDEPTH_API_KEY"]
|
|
53
|
+
app.config["APIDEPTH_ENVIRONMENT"] = "production"
|
|
54
|
+
|
|
55
|
+
Apidepth(app)
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Standalone / scripts
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
import apidepth
|
|
62
|
+
from apidepth import registry_loader
|
|
63
|
+
|
|
64
|
+
apidepth.configure(
|
|
65
|
+
api_key=os.environ["APIDEPTH_API_KEY"],
|
|
66
|
+
environment="production",
|
|
67
|
+
)
|
|
68
|
+
apidepth.instrument() # call before any outbound HTTP
|
|
69
|
+
registry_loader.load_and_start() # loads remote vendor registry + starts refresh thread
|
|
70
|
+
|
|
71
|
+
import requests
|
|
72
|
+
resp = requests.get("https://api.stripe.com/v1/charges/ch_abc123", ...)
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
`load_and_start()` fetches the latest vendor registry from the network (with a local disk cache fallback) and starts a background refresh thread. Without it, only the six bundled vendors are recognised. Django and Flask integrations call this automatically.
|
|
76
|
+
|
|
77
|
+
For **Gunicorn / uWSGI**, call `Collector.register_fork_safety()` once before the server forks so each worker gets its own flush thread:
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
from apidepth.collector import Collector
|
|
81
|
+
Collector.register_fork_safety()
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## Configuration
|
|
87
|
+
|
|
88
|
+
| Option | Default | Description |
|
|
89
|
+
|---|---|---|
|
|
90
|
+
| `api_key` | `None` | **Required.** Your Apidepth API key. |
|
|
91
|
+
| `environment` | `None` | Deployment environment tag, e.g. `"production"`. |
|
|
92
|
+
| `enabled` | `True` | Set `False` to disable all instrumentation. |
|
|
93
|
+
| `sample_rate` | `1.0` | Float 0.0–1.0. Fraction of requests to capture. |
|
|
94
|
+
| `ignored_hosts` | `[]` | List of hostnames to never record. |
|
|
95
|
+
| `extra_vendors` | `{}` | Map `{"vendor-name": "host"}` for in-house APIs. |
|
|
96
|
+
| `flush_interval` | `20` | Background flush interval in seconds. |
|
|
97
|
+
| `registry_cache_path` | `/tmp/apidepth_registry.json` | Disk cache for the vendor registry. |
|
|
98
|
+
| `registry_refresh_interval` | `21600` | Registry refresh interval in seconds (6 h). |
|
|
99
|
+
| `on_flush_error` | `None` | `Callable(exc, ctx)` for routing errors to Sentry etc. |
|
|
100
|
+
| `collector_url` | production endpoint | Override for self-hosted collectors. |
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## What gets captured
|
|
105
|
+
|
|
106
|
+
Every outbound HTTP request to a recognised vendor produces one event:
|
|
107
|
+
|
|
108
|
+
| Field | Example |
|
|
109
|
+
|---|---|
|
|
110
|
+
| `vendor` | `"stripe"` |
|
|
111
|
+
| `endpoint` | `"/v1/charges/:id"` |
|
|
112
|
+
| `method` | `"POST"` |
|
|
113
|
+
| `status` | `200` |
|
|
114
|
+
| `outcome` | `"success"` / `"client_error"` / `"server_error"` / `"timeout"` |
|
|
115
|
+
| `duration_ms` | `234` |
|
|
116
|
+
| `cold_start` | `false` (always — see [Known differences](#known-differences-from-the-ruby-gem)) |
|
|
117
|
+
| `env` | `"production"` |
|
|
118
|
+
| `ts` | `1747008000000` (epoch ms) |
|
|
119
|
+
| `rl_remaining` | `4999` (when rate limit headers present) |
|
|
120
|
+
| `rl_limit` | `5000` |
|
|
121
|
+
| `rl_reset_at` | `1747008060000` (epoch ms) |
|
|
122
|
+
|
|
123
|
+
**Never captured:** request/response bodies, headers, query parameters, credentials.
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## Supported vendors
|
|
128
|
+
|
|
129
|
+
| Vendor | Host |
|
|
130
|
+
|---|---|
|
|
131
|
+
| Stripe | `api.stripe.com` |
|
|
132
|
+
| OpenAI | `api.openai.com` |
|
|
133
|
+
| Anthropic | `api.anthropic.com` |
|
|
134
|
+
| Twilio | `api.twilio.com` |
|
|
135
|
+
| Resend | `api.resend.com` |
|
|
136
|
+
| GitHub | `api.github.com` |
|
|
137
|
+
|
|
138
|
+
Additional vendors are loaded from the remote registry every 6 hours.
|
|
139
|
+
|
|
140
|
+
### Custom vendors
|
|
141
|
+
|
|
142
|
+
```python
|
|
143
|
+
apidepth.configure(
|
|
144
|
+
extra_vendors={"payments-api": "api.payments.internal"},
|
|
145
|
+
)
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## Rate limit tracking
|
|
151
|
+
|
|
152
|
+
The SDK extracts quota state from response headers and includes it in every event.
|
|
153
|
+
Supported header families (checked in priority order):
|
|
154
|
+
|
|
155
|
+
- **OpenAI / Anthropic**: `x-ratelimit-remaining-requests`, `x-ratelimit-limit-requests`, `x-ratelimit-reset-requests`
|
|
156
|
+
- **GitHub**: `x-ratelimit-remaining`, `x-ratelimit-limit`, `x-ratelimit-reset`
|
|
157
|
+
- **IETF draft / HubSpot**: `ratelimit-remaining`, `ratelimit-limit`, `ratelimit-reset`
|
|
158
|
+
- **Stripe / generic 429**: `retry-after`
|
|
159
|
+
|
|
160
|
+
Reset values are normalised to epoch milliseconds regardless of the source format (Unix timestamp, seconds-from-now, OpenAI duration strings like `"1m30s"`).
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## Debugging
|
|
165
|
+
|
|
166
|
+
```python
|
|
167
|
+
from apidepth.collector import Collector
|
|
168
|
+
|
|
169
|
+
print(Collector.instance().stats())
|
|
170
|
+
# {
|
|
171
|
+
# 'queue_size': 0,
|
|
172
|
+
# 'consecutive_failures': 0,
|
|
173
|
+
# 'total_dropped': 0,
|
|
174
|
+
# 'last_flush_at': 1747008000.123,
|
|
175
|
+
# }
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
## Framework compatibility
|
|
181
|
+
|
|
182
|
+
| Framework | Version | Integration |
|
|
183
|
+
|---|---|---|
|
|
184
|
+
| Django | 3.2+ | `apidepth.integrations.django` in `INSTALLED_APPS` |
|
|
185
|
+
| Flask | 2.0+ | `Apidepth(app)` |
|
|
186
|
+
| FastAPI / Starlette | any | Call `apidepth.instrument()` at startup |
|
|
187
|
+
| Scripts / workers | — | Call `apidepth.instrument()` at startup |
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
## Python compatibility
|
|
192
|
+
|
|
193
|
+
Python 3.9–3.13. No required runtime dependencies (stdlib only). `requests` and `httpx` are optional instrumentation targets detected at runtime.
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
## Known differences from the Ruby gem
|
|
198
|
+
|
|
199
|
+
### `cold_start` is always `false`
|
|
200
|
+
|
|
201
|
+
The Ruby gem tags the **first** outbound request on each TCP connection with `cold_start: true` using `Net::HTTP#started?`. The Apidepth collector uses this flag to exclude DNS + TCP + TLS handshake overhead from latency percentile calculations (p50/p95/p99), keeping those metrics representative of steady-state vendor performance.
|
|
202
|
+
|
|
203
|
+
Neither `requests` (backed by urllib3) nor `httpx` exposes a public API for detecting whether the underlying socket is a keep-alive reuse. The Python SDK therefore always sends `cold_start: false`.
|
|
204
|
+
|
|
205
|
+
**Practical impact depends on your traffic pattern:**
|
|
206
|
+
|
|
207
|
+
| Traffic pattern | Impact |
|
|
208
|
+
|---|---|
|
|
209
|
+
| High-throughput web service | **Negligible** — cold starts are a tiny fraction of total requests; percentile inflation is unmeasurable |
|
|
210
|
+
| Low-throughput service / cron job | **Noticeable** — the first request per run pays ~50–200 ms of connection overhead that isn't excluded from percentiles; p95/p99 may read slightly higher than in Ruby |
|
|
211
|
+
| Serverless / short-lived worker | **Material** — every invocation starts cold; all latency data includes connection overhead; comparisons against Ruby-instrumented services will show the Python side as systematically higher |
|
|
212
|
+
|
|
213
|
+
The raw duration values are accurate — only the percentile statistics are affected. If cold-start exclusion matters for your environment, filter those events manually in your dashboard (e.g. a warm-up request flag in thread-local state) until the underlying libraries expose the required connection-state API.
|