apptrail 0.4.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.
Files changed (84) hide show
  1. apptrail-0.4.0/.dockerignore +19 -0
  2. apptrail-0.4.0/.github/scripts/check_installed.py +107 -0
  3. apptrail-0.4.0/.github/workflows/ci.yml +75 -0
  4. apptrail-0.4.0/.github/workflows/live.yml +32 -0
  5. apptrail-0.4.0/.github/workflows/publish.yml +84 -0
  6. apptrail-0.4.0/.gitignore +20 -0
  7. apptrail-0.4.0/Dockerfile +22 -0
  8. apptrail-0.4.0/LICENSE +21 -0
  9. apptrail-0.4.0/PKG-INFO +135 -0
  10. apptrail-0.4.0/README.md +113 -0
  11. apptrail-0.4.0/compose.yaml +12 -0
  12. apptrail-0.4.0/docs/AUTHENTICATION.md +39 -0
  13. apptrail-0.4.0/docs/DEVELOPMENT.md +149 -0
  14. apptrail-0.4.0/docs/SELF_HOSTING.md +177 -0
  15. apptrail-0.4.0/docs/images/apptrail-banner.png +0 -0
  16. apptrail-0.4.0/pyproject.toml +75 -0
  17. apptrail-0.4.0/pyproject.toml.orig +60 -0
  18. apptrail-0.4.0/src/apptrail/__init__.py +3 -0
  19. apptrail-0.4.0/src/apptrail/api.py +674 -0
  20. apptrail-0.4.0/src/apptrail/auth.py +339 -0
  21. apptrail-0.4.0/src/apptrail/cli.py +120 -0
  22. apptrail-0.4.0/src/apptrail/config.py +53 -0
  23. apptrail-0.4.0/src/apptrail/db.py +424 -0
  24. apptrail-0.4.0/src/apptrail/engines.py +414 -0
  25. apptrail-0.4.0/src/apptrail/insights.py +217 -0
  26. apptrail-0.4.0/src/apptrail/insights_api.py +214 -0
  27. apptrail-0.4.0/src/apptrail/listing_history.py +368 -0
  28. apptrail-0.4.0/src/apptrail/matching.py +113 -0
  29. apptrail-0.4.0/src/apptrail/migrations/001_initial.sql +118 -0
  30. apptrail-0.4.0/src/apptrail/regions.json +1295 -0
  31. apptrail-0.4.0/src/apptrail/regions.py +18 -0
  32. apptrail-0.4.0/src/apptrail/service.py +671 -0
  33. apptrail-0.4.0/src/apptrail/static/app.js +2624 -0
  34. apptrail-0.4.0/src/apptrail/static/auth.css +65 -0
  35. apptrail-0.4.0/src/apptrail/static/auth.html +89 -0
  36. apptrail-0.4.0/src/apptrail/static/auth.js +103 -0
  37. apptrail-0.4.0/src/apptrail/static/competitors.js +396 -0
  38. apptrail-0.4.0/src/apptrail/static/favicon.svg +18 -0
  39. apptrail-0.4.0/src/apptrail/static/history-chart.js +71 -0
  40. apptrail-0.4.0/src/apptrail/static/index.html +198 -0
  41. apptrail-0.4.0/src/apptrail/static/insights-data.js +119 -0
  42. apptrail-0.4.0/src/apptrail/static/insights-ui.js +559 -0
  43. apptrail-0.4.0/src/apptrail/static/loading.js +56 -0
  44. apptrail-0.4.0/src/apptrail/static/style.css +4337 -0
  45. apptrail-0.4.0/src/apptrail/static/tracking.js +153 -0
  46. apptrail-0.4.0/src/apptrail/static/vendor/chart-LICENSE.md +9 -0
  47. apptrail-0.4.0/src/apptrail/static/vendor/chart.umd.min.js +20 -0
  48. apptrail-0.4.0/src/apptrail/static/vendor/lucide-LICENSE.txt +43 -0
  49. apptrail-0.4.0/src/apptrail/static/vendor/lucide-icons.js +108 -0
  50. apptrail-0.4.0/src/apptrail/worker.py +204 -0
  51. apptrail-0.4.0/tests/competitors.test.mjs +88 -0
  52. apptrail-0.4.0/tests/conftest.py +179 -0
  53. apptrail-0.4.0/tests/history-chart.test.mjs +129 -0
  54. apptrail-0.4.0/tests/insights.test.mjs +110 -0
  55. apptrail-0.4.0/tests/query-groups.test.mjs +33 -0
  56. apptrail-0.4.0/tests/search-progress.test.mjs +93 -0
  57. apptrail-0.4.0/tests/test_audit_regressions.py +127 -0
  58. apptrail-0.4.0/tests/test_auth.py +519 -0
  59. apptrail-0.4.0/tests/test_backend_release_fixes.py +253 -0
  60. apptrail-0.4.0/tests/test_claude_backend_regressions.py +131 -0
  61. apptrail-0.4.0/tests/test_cli.py +228 -0
  62. apptrail-0.4.0/tests/test_competitors.py +348 -0
  63. apptrail-0.4.0/tests/test_competitors_browser.py +173 -0
  64. apptrail-0.4.0/tests/test_engine_contracts.py +216 -0
  65. apptrail-0.4.0/tests/test_frontend_release_browser.py +301 -0
  66. apptrail-0.4.0/tests/test_insights.py +426 -0
  67. apptrail-0.4.0/tests/test_insights_browser.py +322 -0
  68. apptrail-0.4.0/tests/test_job_cancellation.py +142 -0
  69. apptrail-0.4.0/tests/test_listing_images.py +264 -0
  70. apptrail-0.4.0/tests/test_live.py +136 -0
  71. apptrail-0.4.0/tests/test_matching.py +140 -0
  72. apptrail-0.4.0/tests/test_onboarding.py +87 -0
  73. apptrail-0.4.0/tests/test_persistence.py +233 -0
  74. apptrail-0.4.0/tests/test_query_management.py +147 -0
  75. apptrail-0.4.0/tests/test_query_management_browser.py +92 -0
  76. apptrail-0.4.0/tests/test_regions.py +223 -0
  77. apptrail-0.4.0/tests/test_regions_live.py +71 -0
  78. apptrail-0.4.0/tests/test_release_csv.py +56 -0
  79. apptrail-0.4.0/tests/test_release_dashboard.py +91 -0
  80. apptrail-0.4.0/tests/test_release_regressions.py +149 -0
  81. apptrail-0.4.0/tests/test_search_progress.py +47 -0
  82. apptrail-0.4.0/tests/test_workflows.py +275 -0
  83. apptrail-0.4.0/tests/tracking.test.mjs +35 -0
  84. apptrail-0.4.0/uv.lock +1042 -0
@@ -0,0 +1,19 @@
1
+ .git
2
+ .venv
3
+ .env
4
+ __pycache__
5
+ *.sqlite3*
6
+ .apptrail
7
+ test-results
8
+ dist
9
+ .pytest_cache
10
+ .ruff_cache
11
+ tests
12
+ docs
13
+ temp
14
+ .idea
15
+ .DS_Store
16
+ .playwright-mcp
17
+ credentials.json
18
+ setup-token
19
+ .env.*
@@ -0,0 +1,107 @@
1
+ """Boot the installed wheel outside the checkout and exercise its packaged UI."""
2
+
3
+ import http.cookiejar
4
+ import json
5
+ import os
6
+ import re
7
+ import subprocess
8
+ import sys
9
+ import tempfile
10
+ import time
11
+ import urllib.error
12
+ import urllib.request
13
+ from pathlib import Path
14
+
15
+ import apptrail
16
+
17
+ checkout = Path(__file__).resolve().parents[2]
18
+ assert not Path(apptrail.__file__).resolve().is_relative_to(checkout / "src"), (
19
+ "This smoke check must run against the installed wheel, not an editable checkout."
20
+ )
21
+ environment = {
22
+ key: value
23
+ for key, value in os.environ.items()
24
+ if key not in {"SERPAPI_KEY", "SERPAPI_API_KEY", "APPTRAIL_ORIGIN", "PYTHONPATH"}
25
+ }
26
+
27
+ with tempfile.TemporaryDirectory(prefix="apptrail-wheel-") as temporary:
28
+ directory = Path(temporary)
29
+ with (directory / "server.log").open("w+") as log:
30
+ process = subprocess.Popen(
31
+ [sys.executable, "-m", "apptrail.cli", "--no-browser", "--data-dir", str(directory)],
32
+ cwd=directory,
33
+ env=environment,
34
+ stdout=log,
35
+ stderr=log,
36
+ )
37
+ try:
38
+ deadline = time.monotonic() + 15
39
+ url = None
40
+ while time.monotonic() < deadline:
41
+ log.seek(0)
42
+ output = log.read()
43
+ if process.poll() is not None:
44
+ raise AssertionError(f"Installed app exited during startup:\n{output}")
45
+ match = re.search(r"Web UI: (http://[^\s]+)", output)
46
+ if match:
47
+ url = match.group(1)
48
+ try:
49
+ with urllib.request.urlopen(url + "/healthz", timeout=0.5) as response:
50
+ if json.load(response)["status"] == "ok":
51
+ break
52
+ except (OSError, urllib.error.URLError):
53
+ pass
54
+ time.sleep(0.05)
55
+ else:
56
+ raise AssertionError(f"Installed app did not become ready:\n{output}")
57
+
58
+ browser = urllib.request.build_opener(
59
+ urllib.request.HTTPCookieProcessor(http.cookiejar.CookieJar())
60
+ )
61
+ try:
62
+ browser.open(url + "/api/state", timeout=5)
63
+ except urllib.error.HTTPError as error:
64
+ assert error.code == 401
65
+ else:
66
+ raise AssertionError("The installed app exposed anonymous workspace data")
67
+ request = urllib.request.Request(
68
+ url + "/api/auth/setup",
69
+ data=json.dumps(
70
+ {
71
+ "username": "package-check",
72
+ "password": "Package smoke test password 1!",
73
+ "setup_token": (directory / "setup-token").read_text(),
74
+ }
75
+ ).encode(),
76
+ headers={"Content-Type": "application/json", "X-AppTrail-Request": "1"},
77
+ )
78
+ with browser.open(request, timeout=5) as response:
79
+ assert json.load(response)["csrf_token"]
80
+ with browser.open(url + "/api/state", timeout=5) as response:
81
+ state = json.load(response)
82
+ assert state["apps"] == [] and state["monitors"] == []
83
+ assert state["configured"] is False
84
+ with browser.open(url + "/api/regions", timeout=5) as response:
85
+ assert json.load(response)["countries"]["gb"] == "United Kingdom"
86
+ for path in (
87
+ "/",
88
+ "/static/app.js",
89
+ "/static/history-chart.js",
90
+ "/static/tracking.js",
91
+ "/static/insights-data.js",
92
+ "/static/insights-ui.js",
93
+ "/static/vendor/chart.umd.min.js",
94
+ "/static/vendor/chart-LICENSE.md",
95
+ ):
96
+ with browser.open(url + path, timeout=5) as response:
97
+ assert response.status == 200 and response.read(), path
98
+ print(
99
+ "Installed wheel passed startup, owner setup, region catalog, state, and UI assets."
100
+ )
101
+ finally:
102
+ process.terminate()
103
+ try:
104
+ process.wait(timeout=10)
105
+ except subprocess.TimeoutExpired:
106
+ process.kill()
107
+ process.wait(timeout=5)
@@ -0,0 +1,75 @@
1
+ name: Tests and package
2
+ on:
3
+ pull_request:
4
+ branches: [main]
5
+ push:
6
+ branches: [main]
7
+ workflow_dispatch:
8
+ permissions:
9
+ contents: read
10
+ concurrency:
11
+ group: ci-${{ github.event.pull_request.number || github.ref }}
12
+ cancel-in-progress: true
13
+ jobs:
14
+ test:
15
+ runs-on: ${{ matrix.os }}
16
+ timeout-minutes: 10
17
+ strategy:
18
+ fail-fast: false
19
+ matrix:
20
+ os: [ubuntu-latest]
21
+ python: ["3.11", "3.12", "3.13", "3.14"]
22
+ include:
23
+ - os: macos-latest
24
+ python: "3.12"
25
+ steps:
26
+ - uses: actions/checkout@v4
27
+ - uses: astral-sh/setup-uv@v6
28
+ with:
29
+ python-version: ${{ matrix.python }}
30
+ - run: uv sync --locked
31
+ - run: uv run pytest -m "not live and not browser" --strict-markers --durations=10
32
+ - run: uv run ruff check src tests .github/scripts
33
+ - run: uv run ruff format --check src tests .github/scripts
34
+ - run: uv build
35
+ - name: Check packaged UI and migrations
36
+ run: |
37
+ uv run python - <<'PY'
38
+ from pathlib import Path
39
+ from zipfile import ZipFile
40
+ wheel = next(Path("dist").glob("*.whl"))
41
+ with ZipFile(wheel) as archive:
42
+ for name in ("static/auth.html", "static/auth.js", "static/auth.css", "auth.py", "regions.json", "static/index.html", "static/style.css", "static/app.js", "static/history-chart.js", "static/tracking.js", "static/insights-data.js", "static/insights-ui.js", "static/favicon.svg", "static/vendor/chart.umd.min.js", "static/vendor/chart-LICENSE.md", "static/vendor/lucide-icons.js", "static/vendor/lucide-LICENSE.txt", "migrations/001_initial.sql"):
43
+ assert archive.read(f"apptrail/{name}"), name
44
+ license_name = next(name for name in archive.namelist() if name.endswith(".dist-info/licenses/LICENSE"))
45
+ assert archive.read(license_name)
46
+ PY
47
+ - run: uvx --from ./dist/*.whl apptrail --version
48
+ - run: uv run --isolated --no-project --with ./dist/*.whl python .github/scripts/check_installed.py
49
+ - uses: actions/upload-artifact@v4
50
+ with:
51
+ name: distributions-${{ matrix.os }}-${{ matrix.python }}
52
+ path: dist/*
53
+ frontend:
54
+ runs-on: ubuntu-latest
55
+ timeout-minutes: 10
56
+ steps:
57
+ - uses: actions/checkout@v4
58
+ - uses: actions/setup-node@v4
59
+ with:
60
+ node-version: "22"
61
+ - run: node --test tests/*.test.mjs
62
+ - uses: astral-sh/setup-uv@v6
63
+ with:
64
+ python-version: "3.12"
65
+ - run: uv sync --locked
66
+ - run: uv run playwright install --with-deps chromium
67
+ - run: uv run pytest -m browser --strict-markers --durations=10 --basetemp=browser-artifacts
68
+ env:
69
+ APPTRAIL_BROWSER_TESTS: "1"
70
+ APPTRAIL_BROWSER_CHANNEL: ""
71
+ - uses: actions/upload-artifact@v4
72
+ if: failure()
73
+ with:
74
+ name: browser-failure-artifacts
75
+ path: browser-artifacts/**/*.png
@@ -0,0 +1,32 @@
1
+ name: Live SerpApi integration tests
2
+ on:
3
+ pull_request:
4
+ branches: [main]
5
+ push:
6
+ branches: [main]
7
+ workflow_dispatch:
8
+ permissions:
9
+ contents: read
10
+ concurrency:
11
+ group: live-${{ github.event.pull_request.number || github.ref }}
12
+ cancel-in-progress: true
13
+ jobs:
14
+ live:
15
+ if: >-
16
+ github.actor != 'dependabot[bot]' &&
17
+ (github.event_name != 'pull_request' ||
18
+ github.event.pull_request.head.repo.full_name == github.repository)
19
+ runs-on: ubuntu-latest
20
+ timeout-minutes: 20
21
+ steps:
22
+ - uses: actions/checkout@v4
23
+ - uses: astral-sh/setup-uv@v6
24
+ with:
25
+ python-version: "3.12"
26
+ - run: uv sync --locked
27
+ - name: Run live tests with the repository secret
28
+ run: |
29
+ uv run python -c 'import os; assert os.getenv("SERPAPI_KEY"), "Configure the SERPAPI_KEY repository secret before running live tests."'
30
+ uv run pytest -m live -v --tb=short --strict-markers --durations=10
31
+ env:
32
+ SERPAPI_KEY: ${{ secrets.SERPAPI_KEY }}
@@ -0,0 +1,84 @@
1
+ name: Publish to PyPI
2
+ on:
3
+ release:
4
+ types: [published]
5
+ permissions:
6
+ contents: read
7
+ concurrency:
8
+ group: pypi-${{ github.event.release.tag_name }}
9
+ cancel-in-progress: false
10
+ jobs:
11
+ build:
12
+ if: startsWith(github.event.release.tag_name, 'v')
13
+ runs-on: ubuntu-latest
14
+ timeout-minutes: 15
15
+ steps:
16
+ - uses: actions/checkout@v4
17
+ with:
18
+ fetch-depth: 0
19
+ persist-credentials: false
20
+ - name: Verify the release commit belongs to main
21
+ run: |
22
+ if ! git merge-base --is-ancestor HEAD origin/main; then
23
+ echo "::error::The release tag must point to a commit on main."
24
+ exit 1
25
+ fi
26
+ - uses: astral-sh/setup-uv@v6
27
+ with:
28
+ python-version: "3.12"
29
+ - run: uv sync --locked
30
+ - name: Verify the release tag matches the package version
31
+ env:
32
+ RELEASE_TAG: ${{ github.event.release.tag_name }}
33
+ run: |
34
+ uv run python - <<'PY'
35
+ import os
36
+ import tomllib
37
+ from pathlib import Path
38
+
39
+ from apptrail import __version__
40
+
41
+ version = tomllib.loads(Path("pyproject.toml").read_text())["project"]["version"]
42
+ expected = f"v{version}"
43
+ if os.environ["RELEASE_TAG"] != expected:
44
+ raise SystemExit(f"Release tag must be {expected} to match pyproject.toml.")
45
+ if __version__ != version:
46
+ raise SystemExit("src/apptrail/__init__.py must match the package version.")
47
+ PY
48
+ - run: uv run pytest -m "not live and not browser" --strict-markers --durations=10
49
+ - uses: actions/setup-node@v4
50
+ with:
51
+ node-version: "22"
52
+ - run: node --test tests/*.test.mjs
53
+ - run: uv run playwright install --with-deps chromium
54
+ - run: uv run pytest -m browser --strict-markers --durations=10
55
+ env:
56
+ APPTRAIL_BROWSER_TESTS: "1"
57
+ APPTRAIL_BROWSER_CHANNEL: ""
58
+ - run: uv build
59
+ - run: uv run --isolated --no-project --with ./dist/*.whl python .github/scripts/check_installed.py
60
+ - run: uv run --isolated --no-project --with ./dist/*.tar.gz python .github/scripts/check_installed.py
61
+ - uses: actions/upload-artifact@v4
62
+ with:
63
+ name: python-distributions
64
+ path: dist/*
65
+ if-no-files-found: error
66
+
67
+ publish:
68
+ needs: build
69
+ runs-on: ubuntu-latest
70
+ timeout-minutes: 5
71
+ environment:
72
+ name: pypi
73
+ url: https://pypi.org/project/apptrail/
74
+ permissions:
75
+ id-token: write
76
+ steps:
77
+ - uses: astral-sh/setup-uv@v6
78
+ with:
79
+ enable-cache: false
80
+ - uses: actions/download-artifact@v4
81
+ with:
82
+ name: python-distributions
83
+ path: dist/
84
+ - run: uv publish --trusted-publishing always
@@ -0,0 +1,20 @@
1
+ .venv/
2
+ __pycache__/
3
+ *.py[cod]
4
+ .pytest_cache/
5
+ .ruff_cache/
6
+ .env
7
+ dist/
8
+ *.sqlite3*
9
+ .apptrail/
10
+ test-results/
11
+ .playwright-mcp/
12
+ *.egg-info/
13
+ .idea/
14
+ .DS_Store
15
+ credentials.json
16
+ setup-token
17
+ .env.*
18
+ !.env.example
19
+
20
+ /temp/
@@ -0,0 +1,22 @@
1
+ FROM python:3.14-slim AS builder
2
+ COPY --from=ghcr.io/astral-sh/uv:0.12.19 /uv /usr/local/bin/uv
3
+ WORKDIR /build
4
+ ENV UV_COMPILE_BYTECODE=1 UV_LINK_MODE=copy UV_PROJECT_ENVIRONMENT=/opt/venv
5
+ COPY pyproject.toml uv.lock README.md LICENSE ./
6
+ COPY src ./src
7
+ RUN uv sync --locked --no-dev --no-install-project \
8
+ && uv build --wheel \
9
+ && uv pip install --python /opt/venv/bin/python --no-deps dist/*.whl
10
+
11
+ FROM python:3.14-slim
12
+ RUN groupadd --gid 10001 apptrail && useradd --uid 10001 --gid apptrail --create-home apptrail \
13
+ && mkdir /data && chown apptrail:apptrail /data
14
+ COPY --from=builder /opt/venv /opt/venv
15
+ ENV PATH="/opt/venv/bin:$PATH" APPTRAIL_DATA_DIR=/data PYTHONUNBUFFERED=1
16
+ USER apptrail
17
+ VOLUME ["/data"]
18
+ EXPOSE 80
19
+ HEALTHCHECK --interval=30s --timeout=5s --start-period=10s \
20
+ CMD python -c "import urllib.request; urllib.request.urlopen('http://127.0.0.1:80/healthz', timeout=4)" || exit 1
21
+ ENTRYPOINT ["apptrail"]
22
+ CMD ["--host", "0.0.0.0", "--port", "80", "--no-browser"]
apptrail-0.4.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 AppTrail contributors
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.
@@ -0,0 +1,135 @@
1
+ Metadata-Version: 2.4
2
+ Name: apptrail
3
+ Version: 0.4.0
4
+ Summary: Open-source app visibility tracking across the App Store, Google Play, and AI search.
5
+ License-Expression: MIT
6
+ License-File: LICENSE
7
+ Requires-Dist: fastapi>=0.115,<1
8
+ Requires-Dist: uvicorn>=0.30,<1
9
+ Requires-Dist: serpapi>=0.1.5,<1
10
+ Requires-Dist: sqlalchemy>=2.0,<3
11
+ Requires-Dist: platformdirs>=4,<5
12
+ Requires-Dist: filelock>=3.16,<4
13
+ Requires-Dist: python-dateutil>=2.9,<3
14
+ Requires-Dist: argon2-cffi>=25.1,<26
15
+ Requires-Dist: pillow>=12,<13
16
+ Requires-Python: >=3.11
17
+ Project-URL: Homepage, https://github.com/serpapi/apptrail
18
+ Project-URL: Documentation, https://github.com/serpapi/apptrail/blob/main/README.md
19
+ Project-URL: Repository, https://github.com/serpapi/apptrail
20
+ Project-URL: Issues, https://github.com/serpapi/apptrail/issues
21
+ Description-Content-Type: text/markdown
22
+
23
+ # AppTrail
24
+
25
+ **Open-source app visibility tracking across the App Store, Google Play, and AI search.**
26
+
27
+ ![AppTrail dashboard](https://raw.githubusercontent.com/serpapi/apptrail/main/docs/images/apptrail-banner.png)
28
+
29
+ Your next user might search the App Store, browse Google Play, or ask an AI which app to use. AppTrail follows those searches and keeps a history of where your app appears.
30
+
31
+ Connect your [SerpApi](https://serpapi.com/) account, choose your app, and add the keywords and questions you want to follow. Open any check to see the saved results and the evidence behind a match.
32
+
33
+ ## What you can track
34
+
35
+ - **Store rankings.** Follow your app's position for the keywords people use in the App Store and Google Play. See which searches bring your app near the top and how those positions change over time.
36
+ - **Competitor comparisons.** Track competing apps alongside your own for the same keywords and AI questions. See where you lead, where competitors appear ahead of you, and how the gap changes.
37
+ - **Countries and regions.** Follow store rankings and Google AI visibility in the countries you care about. Compare the same keyword or question across markets to see where your app is easier to find.
38
+ - **AI mentions and citations.** See whether Google AI Mode, Google AI Overviews, and Bing Copilot mention your app when answering questions. Read the saved answers and check whether they link to your store listing or website.
39
+ - **Listing history.** Keep a timeline of changes to your app's or a competitor's store listing. Compare titles, descriptions, pricing, icons, and screenshots to see what changed between checks.
40
+ - **Ranking alerts.** Get in-app notifications when your app loses ground after ranking in the top 10. See which keyword and country changed, then open the saved results.
41
+ - **Scheduled or manual checks.** Run checks on demand, or schedule them daily, weekly, every two weeks, or monthly. Review expected credit usage before starting and see your remaining SerpApi balance in Settings.
42
+ - **Your history to keep.** Export results as CSV files for your own analysis and download backups of your workspace, including saved history.
43
+
44
+ ## Run AppTrail
45
+
46
+ You need Python 3.11 or newer and a [SerpApi API key](https://serpapi.com/manage-api-key). Enter the key in the setup wizard. Docker includes Python.
47
+
48
+ Run [apptrail](https://pypi.org/project/apptrail/) with uvx, or install it with pip.
49
+
50
+ ### uvx
51
+
52
+ ```bash
53
+ uvx apptrail
54
+ ```
55
+
56
+ ### pip
57
+
58
+ ```bash
59
+ pip install apptrail
60
+ apptrail
61
+ ```
62
+
63
+ Both commands open the web interface on a free localhost port. On first launch, use the setup code printed in the terminal to create your owner account. Sign in before connecting SerpApi or accessing the dashboard. Your account and database stay in a shared user data directory across restarts and package upgrades.
64
+
65
+ ### Docker
66
+
67
+ Run the image from [Docker Hub](https://hub.docker.com/r/serpapi/apptrail):
68
+
69
+ ```bash
70
+ docker run -d --name apptrail \
71
+ -p 80:80 \
72
+ -v apptrail-data:/data \
73
+ --restart unless-stopped \
74
+ --stop-timeout 360 \
75
+ serpapi/apptrail:latest
76
+ ```
77
+
78
+ Run `docker logs apptrail` to find the one-time setup code, then open [localhost](http://localhost), or `http://SERVER_IP` for a remote server, and create your owner account. Connect SerpApi after signing in. The named volume preserves your account and data when the container is replaced.
79
+
80
+ The image supports Intel/AMD and ARM Linux. If port `80` is already in use, change the mapping to `-p 8080:80` and open `http://localhost:8080`. See [self-hosting](https://github.com/serpapi/apptrail/blob/main/docs/SELF_HOSTING.md) for CapRover, Coolify, Docker Compose, building from source, HTTPS, and backups.
81
+
82
+ ## Get Started
83
+
84
+ ### Set up tracking
85
+
86
+ 1. Create your AppTrail owner account using the server’s setup code, then connect SerpApi and verify your key.
87
+ 2. Search for your iOS or Android app, or paste its store URL. Select at least one listing; AppTrail verifies and saves its identifier.
88
+ 3. Add store keywords such as “habit tracker” and AI questions such as “Which is the best iOS app for building a daily routine?”
89
+ 4. Choose one or more countries, a language, and a refresh frequency. Review the usage estimate and start tracking.
90
+
91
+ The first checks run immediately. Keep the AppTrail process or Docker container running for automatic checks; the browser can be closed. If the process stops, your history remains saved and overdue checks resume when it starts again. Missed historical results cannot be reconstructed.
92
+
93
+ Skipped queries during setup? The **Finish setting up** checklist on Overview links directly to store keywords and AI questions. Each step is optional, and the checklist closes when every step is completed or skipped. Reopen it from **Settings → Setup checklist**.
94
+
95
+ To compare countries, open the Overview chart and choose **Compare → Countries**. Choose a store or AI source, narrow to a keyword or question, and select the countries to show. Bing Copilot has global results and is available in the Sources view.
96
+
97
+ ### Compare competitors
98
+
99
+ Choose **Add competitors** beneath any search in Overview, Store rankings, or AI visibility. The **Apps & competitors** panel shows each app’s position or AI mentions and citations from the same saved check. Select apps from your workspace, or use **Find a competitor** to verify a new app’s store listing.
100
+
101
+ Adding an existing app to a search reuses saved results and shares future checks, so it does not use extra tracking searches. Discovering a new app, verifying its listings, and manual store-detail refreshes can use separate credits. Store comparisons require a verified listing on the matching platform.
102
+
103
+ ### Explore changes
104
+
105
+ Open **Apps & competitors → Open query matrix** beneath a query to compare saved results across its countries. Each cell includes its check time and opens the underlying evidence. Different languages, devices, and search depths stay separate.
106
+
107
+ On Overview, switch the history panel from **Trend** to **Distribution** to see counts and percentages for positions 1–3, 4–10, 11–50, 51+, and results where the app was not found. Click a group to inspect its queries. Changes compare the latest check for each app/query combination with the preceding equal-length period. Only combinations checked successfully in both periods contribute to the change counts; failed checks and featured-only placements are shown separately.
108
+
109
+ The notification bell reports a drop of at least five positions from a previous top-10 rank, an exit from the top 10, or disappearance from a sufficiently deep checked result set. Two successful checks must confirm a loss. An intervening failed check resets confirmation, and continuing losses do not produce duplicate alerts until the ranking recovers. Notifications, matrices, and distributions use saved search data and consume no extra SerpApi credits.
110
+
111
+ ### Track listing history
112
+
113
+ Open **Listing history → Track a listing**. Collection is off by default. Select an app, store, country, and frequency; Google Play also supports a language choice. The credit estimate appears before you enable tracking. Each scheduled check uses one SerpApi product request, with an initial baseline check when tracking starts. Manual checks and retries can consume additional credits.
114
+
115
+ The timeline highlights changes to the fields returned by the store, including titles, descriptions, versions, pricing, and images. Text comparisons highlight additions and removals. Screenshots can be compared in order, with added and moved images labeled. Supported images are archived locally in the database and included in backups. If an image cannot be archived, the comparison shows an unavailable-image placeholder. AppTrail never substitutes the live image for a missing historical image.
116
+
117
+ Use **Manage** to change frequency, pause or resume collection, or request a manual check. Pausing preserves history. Unchanged checks are hidden until you select **Show unchanged checks**. Collection requires the AppTrail process to remain running, and begins when you enable it; earlier listing versions cannot be reconstructed.
118
+
119
+ ### Account access
120
+
121
+ AppTrail requires login for the dashboard and every workspace API, including searches, exports, and backups. First-time setup requires a code from the server terminal, and registration closes after the owner account is created. New server runs require a fresh login. Change your password in Settings, or recover access from the server terminal with `apptrail --reset-password`.
122
+
123
+ See the [self-hosting guide](https://github.com/serpapi/apptrail/blob/main/docs/SELF_HOSTING.md#public-https-hosting) for optional HTTPS proxy configuration and [authentication details](https://github.com/serpapi/apptrail/blob/main/docs/AUTHENTICATION.md).
124
+
125
+ ## A few useful details
126
+
127
+ AppTrail uses your own [SerpApi account](https://serpapi.com/) to fetch data. Discovery, product verification, and tracking requests can consume credits. Identical tracked searches are shared across apps. Settings shows an estimate of monthly usage and your account-wide remaining balance.
128
+
129
+ ### Development
130
+
131
+ To contribute or customize AppTrail, start with the [development guide](https://github.com/serpapi/apptrail/blob/main/docs/DEVELOPMENT.md). It covers local setup with uv, the project layout, running tests, and building packages.
132
+
133
+ ### Self-hosting
134
+
135
+ Keep AppTrail running on your own server with persistent storage for your workspace. The [self-hosting guide](https://github.com/serpapi/apptrail/blob/main/docs/SELF_HOSTING.md) walks through Docker, Docker Compose, CapRover, and Coolify, with instructions for HTTPS, backups, and updates.
@@ -0,0 +1,113 @@
1
+ # AppTrail
2
+
3
+ **Open-source app visibility tracking across the App Store, Google Play, and AI search.**
4
+
5
+ ![AppTrail dashboard](https://raw.githubusercontent.com/serpapi/apptrail/main/docs/images/apptrail-banner.png)
6
+
7
+ Your next user might search the App Store, browse Google Play, or ask an AI which app to use. AppTrail follows those searches and keeps a history of where your app appears.
8
+
9
+ Connect your [SerpApi](https://serpapi.com/) account, choose your app, and add the keywords and questions you want to follow. Open any check to see the saved results and the evidence behind a match.
10
+
11
+ ## What you can track
12
+
13
+ - **Store rankings.** Follow your app's position for the keywords people use in the App Store and Google Play. See which searches bring your app near the top and how those positions change over time.
14
+ - **Competitor comparisons.** Track competing apps alongside your own for the same keywords and AI questions. See where you lead, where competitors appear ahead of you, and how the gap changes.
15
+ - **Countries and regions.** Follow store rankings and Google AI visibility in the countries you care about. Compare the same keyword or question across markets to see where your app is easier to find.
16
+ - **AI mentions and citations.** See whether Google AI Mode, Google AI Overviews, and Bing Copilot mention your app when answering questions. Read the saved answers and check whether they link to your store listing or website.
17
+ - **Listing history.** Keep a timeline of changes to your app's or a competitor's store listing. Compare titles, descriptions, pricing, icons, and screenshots to see what changed between checks.
18
+ - **Ranking alerts.** Get in-app notifications when your app loses ground after ranking in the top 10. See which keyword and country changed, then open the saved results.
19
+ - **Scheduled or manual checks.** Run checks on demand, or schedule them daily, weekly, every two weeks, or monthly. Review expected credit usage before starting and see your remaining SerpApi balance in Settings.
20
+ - **Your history to keep.** Export results as CSV files for your own analysis and download backups of your workspace, including saved history.
21
+
22
+ ## Run AppTrail
23
+
24
+ You need Python 3.11 or newer and a [SerpApi API key](https://serpapi.com/manage-api-key). Enter the key in the setup wizard. Docker includes Python.
25
+
26
+ Run [apptrail](https://pypi.org/project/apptrail/) with uvx, or install it with pip.
27
+
28
+ ### uvx
29
+
30
+ ```bash
31
+ uvx apptrail
32
+ ```
33
+
34
+ ### pip
35
+
36
+ ```bash
37
+ pip install apptrail
38
+ apptrail
39
+ ```
40
+
41
+ Both commands open the web interface on a free localhost port. On first launch, use the setup code printed in the terminal to create your owner account. Sign in before connecting SerpApi or accessing the dashboard. Your account and database stay in a shared user data directory across restarts and package upgrades.
42
+
43
+ ### Docker
44
+
45
+ Run the image from [Docker Hub](https://hub.docker.com/r/serpapi/apptrail):
46
+
47
+ ```bash
48
+ docker run -d --name apptrail \
49
+ -p 80:80 \
50
+ -v apptrail-data:/data \
51
+ --restart unless-stopped \
52
+ --stop-timeout 360 \
53
+ serpapi/apptrail:latest
54
+ ```
55
+
56
+ Run `docker logs apptrail` to find the one-time setup code, then open [localhost](http://localhost), or `http://SERVER_IP` for a remote server, and create your owner account. Connect SerpApi after signing in. The named volume preserves your account and data when the container is replaced.
57
+
58
+ The image supports Intel/AMD and ARM Linux. If port `80` is already in use, change the mapping to `-p 8080:80` and open `http://localhost:8080`. See [self-hosting](https://github.com/serpapi/apptrail/blob/main/docs/SELF_HOSTING.md) for CapRover, Coolify, Docker Compose, building from source, HTTPS, and backups.
59
+
60
+ ## Get Started
61
+
62
+ ### Set up tracking
63
+
64
+ 1. Create your AppTrail owner account using the server’s setup code, then connect SerpApi and verify your key.
65
+ 2. Search for your iOS or Android app, or paste its store URL. Select at least one listing; AppTrail verifies and saves its identifier.
66
+ 3. Add store keywords such as “habit tracker” and AI questions such as “Which is the best iOS app for building a daily routine?”
67
+ 4. Choose one or more countries, a language, and a refresh frequency. Review the usage estimate and start tracking.
68
+
69
+ The first checks run immediately. Keep the AppTrail process or Docker container running for automatic checks; the browser can be closed. If the process stops, your history remains saved and overdue checks resume when it starts again. Missed historical results cannot be reconstructed.
70
+
71
+ Skipped queries during setup? The **Finish setting up** checklist on Overview links directly to store keywords and AI questions. Each step is optional, and the checklist closes when every step is completed or skipped. Reopen it from **Settings → Setup checklist**.
72
+
73
+ To compare countries, open the Overview chart and choose **Compare → Countries**. Choose a store or AI source, narrow to a keyword or question, and select the countries to show. Bing Copilot has global results and is available in the Sources view.
74
+
75
+ ### Compare competitors
76
+
77
+ Choose **Add competitors** beneath any search in Overview, Store rankings, or AI visibility. The **Apps & competitors** panel shows each app’s position or AI mentions and citations from the same saved check. Select apps from your workspace, or use **Find a competitor** to verify a new app’s store listing.
78
+
79
+ Adding an existing app to a search reuses saved results and shares future checks, so it does not use extra tracking searches. Discovering a new app, verifying its listings, and manual store-detail refreshes can use separate credits. Store comparisons require a verified listing on the matching platform.
80
+
81
+ ### Explore changes
82
+
83
+ Open **Apps & competitors → Open query matrix** beneath a query to compare saved results across its countries. Each cell includes its check time and opens the underlying evidence. Different languages, devices, and search depths stay separate.
84
+
85
+ On Overview, switch the history panel from **Trend** to **Distribution** to see counts and percentages for positions 1–3, 4–10, 11–50, 51+, and results where the app was not found. Click a group to inspect its queries. Changes compare the latest check for each app/query combination with the preceding equal-length period. Only combinations checked successfully in both periods contribute to the change counts; failed checks and featured-only placements are shown separately.
86
+
87
+ The notification bell reports a drop of at least five positions from a previous top-10 rank, an exit from the top 10, or disappearance from a sufficiently deep checked result set. Two successful checks must confirm a loss. An intervening failed check resets confirmation, and continuing losses do not produce duplicate alerts until the ranking recovers. Notifications, matrices, and distributions use saved search data and consume no extra SerpApi credits.
88
+
89
+ ### Track listing history
90
+
91
+ Open **Listing history → Track a listing**. Collection is off by default. Select an app, store, country, and frequency; Google Play also supports a language choice. The credit estimate appears before you enable tracking. Each scheduled check uses one SerpApi product request, with an initial baseline check when tracking starts. Manual checks and retries can consume additional credits.
92
+
93
+ The timeline highlights changes to the fields returned by the store, including titles, descriptions, versions, pricing, and images. Text comparisons highlight additions and removals. Screenshots can be compared in order, with added and moved images labeled. Supported images are archived locally in the database and included in backups. If an image cannot be archived, the comparison shows an unavailable-image placeholder. AppTrail never substitutes the live image for a missing historical image.
94
+
95
+ Use **Manage** to change frequency, pause or resume collection, or request a manual check. Pausing preserves history. Unchanged checks are hidden until you select **Show unchanged checks**. Collection requires the AppTrail process to remain running, and begins when you enable it; earlier listing versions cannot be reconstructed.
96
+
97
+ ### Account access
98
+
99
+ AppTrail requires login for the dashboard and every workspace API, including searches, exports, and backups. First-time setup requires a code from the server terminal, and registration closes after the owner account is created. New server runs require a fresh login. Change your password in Settings, or recover access from the server terminal with `apptrail --reset-password`.
100
+
101
+ See the [self-hosting guide](https://github.com/serpapi/apptrail/blob/main/docs/SELF_HOSTING.md#public-https-hosting) for optional HTTPS proxy configuration and [authentication details](https://github.com/serpapi/apptrail/blob/main/docs/AUTHENTICATION.md).
102
+
103
+ ## A few useful details
104
+
105
+ AppTrail uses your own [SerpApi account](https://serpapi.com/) to fetch data. Discovery, product verification, and tracking requests can consume credits. Identical tracked searches are shared across apps. Settings shows an estimate of monthly usage and your account-wide remaining balance.
106
+
107
+ ### Development
108
+
109
+ To contribute or customize AppTrail, start with the [development guide](https://github.com/serpapi/apptrail/blob/main/docs/DEVELOPMENT.md). It covers local setup with uv, the project layout, running tests, and building packages.
110
+
111
+ ### Self-hosting
112
+
113
+ Keep AppTrail running on your own server with persistent storage for your workspace. The [self-hosting guide](https://github.com/serpapi/apptrail/blob/main/docs/SELF_HOSTING.md) walks through Docker, Docker Compose, CapRover, and Coolify, with instructions for HTTPS, backups, and updates.
@@ -0,0 +1,12 @@
1
+ services:
2
+ apptrail:
3
+ build: .
4
+ ports:
5
+ - "80:80"
6
+ volumes:
7
+ - apptrail-data:/data
8
+ restart: unless-stopped
9
+ stop_grace_period: 6m
10
+
11
+ volumes:
12
+ apptrail-data: