agentberlin 0.9.0__py3-none-any.whl
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.
- agentberlin/__init__.py +51 -0
- agentberlin/_http.py +193 -0
- agentberlin/client.py +85 -0
- agentberlin/config.py +19 -0
- agentberlin/exceptions.py +88 -0
- agentberlin/models/__init__.py +53 -0
- agentberlin/models/analytics.py +87 -0
- agentberlin/models/brand.py +40 -0
- agentberlin/models/search.py +85 -0
- agentberlin/models/serp.py +21 -0
- agentberlin/py.typed +0 -0
- agentberlin/resources/__init__.py +15 -0
- agentberlin/resources/analytics.py +40 -0
- agentberlin/resources/brand.py +88 -0
- agentberlin/resources/keywords.py +53 -0
- agentberlin/resources/pages.py +100 -0
- agentberlin/resources/serp.py +57 -0
- agentberlin-0.9.0.dist-info/METADATA +182 -0
- agentberlin-0.9.0.dist-info/RECORD +21 -0
- agentberlin-0.9.0.dist-info/WHEEL +4 -0
- agentberlin-0.9.0.dist-info/licenses/LICENSE +21 -0
agentberlin/__init__.py
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""Agent Berlin Python SDK.
|
|
2
|
+
|
|
3
|
+
A Python SDK for Agent Berlin - AI-powered SEO and AEO automation.
|
|
4
|
+
|
|
5
|
+
Example:
|
|
6
|
+
from agentberlin import AgentBerlin
|
|
7
|
+
|
|
8
|
+
# Set AGENTBERLIN_TOKEN environment variable or pass token directly
|
|
9
|
+
client = AgentBerlin()
|
|
10
|
+
|
|
11
|
+
# Get analytics
|
|
12
|
+
analytics = client.analytics.get(project_domain="example.com")
|
|
13
|
+
|
|
14
|
+
# Search pages
|
|
15
|
+
pages = client.pages.search(project_domain="example.com", query="SEO tips")
|
|
16
|
+
|
|
17
|
+
# Search keywords
|
|
18
|
+
keywords = client.keywords.search(project_domain="example.com", query="marketing")
|
|
19
|
+
|
|
20
|
+
# Get brand profile
|
|
21
|
+
profile = client.brand.get_profile(project_domain="example.com")
|
|
22
|
+
|
|
23
|
+
# Fetch SERP results
|
|
24
|
+
serp = client.serp.fetch(query="best seo tools")
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
from .client import AgentBerlin
|
|
28
|
+
from .exceptions import (
|
|
29
|
+
AgentBerlinAPIError,
|
|
30
|
+
AgentBerlinAuthenticationError,
|
|
31
|
+
AgentBerlinConnectionError,
|
|
32
|
+
AgentBerlinError,
|
|
33
|
+
AgentBerlinNotFoundError,
|
|
34
|
+
AgentBerlinRateLimitError,
|
|
35
|
+
AgentBerlinServerError,
|
|
36
|
+
AgentBerlinValidationError,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
__version__ = "0.9.0"
|
|
40
|
+
|
|
41
|
+
__all__ = [
|
|
42
|
+
"AgentBerlin",
|
|
43
|
+
"AgentBerlinError",
|
|
44
|
+
"AgentBerlinAuthenticationError",
|
|
45
|
+
"AgentBerlinAPIError",
|
|
46
|
+
"AgentBerlinNotFoundError",
|
|
47
|
+
"AgentBerlinRateLimitError",
|
|
48
|
+
"AgentBerlinServerError",
|
|
49
|
+
"AgentBerlinValidationError",
|
|
50
|
+
"AgentBerlinConnectionError",
|
|
51
|
+
]
|
agentberlin/_http.py
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
"""Internal HTTP client for Agent Berlin SDK."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Dict, Optional
|
|
4
|
+
|
|
5
|
+
import requests
|
|
6
|
+
|
|
7
|
+
from .exceptions import (
|
|
8
|
+
AgentBerlinAPIError,
|
|
9
|
+
AgentBerlinAuthenticationError,
|
|
10
|
+
AgentBerlinConnectionError,
|
|
11
|
+
AgentBerlinNotFoundError,
|
|
12
|
+
AgentBerlinRateLimitError,
|
|
13
|
+
AgentBerlinServerError,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
__version__ = "0.1.0"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class HTTPClient:
|
|
20
|
+
"""Internal HTTP client with authentication and error handling."""
|
|
21
|
+
|
|
22
|
+
def __init__(
|
|
23
|
+
self,
|
|
24
|
+
token: str,
|
|
25
|
+
base_url: str,
|
|
26
|
+
timeout: int = 30,
|
|
27
|
+
) -> None:
|
|
28
|
+
self._token = token
|
|
29
|
+
self._base_url = base_url.rstrip("/")
|
|
30
|
+
self._timeout = timeout
|
|
31
|
+
self._session = requests.Session()
|
|
32
|
+
self._session.headers.update(
|
|
33
|
+
{
|
|
34
|
+
"Authorization": f"Bearer {token}",
|
|
35
|
+
"Content-Type": "application/json",
|
|
36
|
+
"User-Agent": f"agentberlin-python/{__version__}",
|
|
37
|
+
}
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
def post(
|
|
41
|
+
self,
|
|
42
|
+
path: str,
|
|
43
|
+
json: Optional[Dict[str, Any]] = None,
|
|
44
|
+
) -> Dict[str, Any]:
|
|
45
|
+
"""Make a POST request.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
path: API endpoint path.
|
|
49
|
+
json: Request body as dict.
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
Response data as dict.
|
|
53
|
+
|
|
54
|
+
Raises:
|
|
55
|
+
AgentBerlinAPIError: On API errors.
|
|
56
|
+
AgentBerlinConnectionError: On network errors.
|
|
57
|
+
"""
|
|
58
|
+
url = f"{self._base_url}{path}"
|
|
59
|
+
|
|
60
|
+
try:
|
|
61
|
+
response = self._session.post(
|
|
62
|
+
url,
|
|
63
|
+
json=json,
|
|
64
|
+
timeout=self._timeout,
|
|
65
|
+
)
|
|
66
|
+
except requests.exceptions.Timeout:
|
|
67
|
+
raise AgentBerlinConnectionError(f"Request timed out after {self._timeout}s")
|
|
68
|
+
except requests.exceptions.ConnectionError as e:
|
|
69
|
+
raise AgentBerlinConnectionError(f"Connection error: {e}")
|
|
70
|
+
except requests.exceptions.RequestException as e:
|
|
71
|
+
raise AgentBerlinConnectionError(f"Request failed: {e}")
|
|
72
|
+
|
|
73
|
+
return self._handle_response(response)
|
|
74
|
+
|
|
75
|
+
def get(
|
|
76
|
+
self,
|
|
77
|
+
path: str,
|
|
78
|
+
params: Optional[Dict[str, Any]] = None,
|
|
79
|
+
) -> Dict[str, Any]:
|
|
80
|
+
"""Make a GET request.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
path: API endpoint path.
|
|
84
|
+
params: Query parameters.
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
Response data as dict.
|
|
88
|
+
|
|
89
|
+
Raises:
|
|
90
|
+
AgentBerlinAPIError: On API errors.
|
|
91
|
+
AgentBerlinConnectionError: On network errors.
|
|
92
|
+
"""
|
|
93
|
+
url = f"{self._base_url}{path}"
|
|
94
|
+
|
|
95
|
+
try:
|
|
96
|
+
response = self._session.get(
|
|
97
|
+
url,
|
|
98
|
+
params=params,
|
|
99
|
+
timeout=self._timeout,
|
|
100
|
+
)
|
|
101
|
+
except requests.exceptions.Timeout:
|
|
102
|
+
raise AgentBerlinConnectionError(f"Request timed out after {self._timeout}s")
|
|
103
|
+
except requests.exceptions.ConnectionError as e:
|
|
104
|
+
raise AgentBerlinConnectionError(f"Connection error: {e}")
|
|
105
|
+
except requests.exceptions.RequestException as e:
|
|
106
|
+
raise AgentBerlinConnectionError(f"Request failed: {e}")
|
|
107
|
+
|
|
108
|
+
return self._handle_response(response)
|
|
109
|
+
|
|
110
|
+
def patch(
|
|
111
|
+
self,
|
|
112
|
+
path: str,
|
|
113
|
+
json: Optional[Dict[str, Any]] = None,
|
|
114
|
+
) -> Dict[str, Any]:
|
|
115
|
+
"""Make a PATCH request.
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
path: API endpoint path.
|
|
119
|
+
json: Request body as dict.
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
Response data as dict.
|
|
123
|
+
|
|
124
|
+
Raises:
|
|
125
|
+
AgentBerlinAPIError: On API errors.
|
|
126
|
+
AgentBerlinConnectionError: On network errors.
|
|
127
|
+
"""
|
|
128
|
+
url = f"{self._base_url}{path}"
|
|
129
|
+
|
|
130
|
+
try:
|
|
131
|
+
response = self._session.patch(
|
|
132
|
+
url,
|
|
133
|
+
json=json,
|
|
134
|
+
timeout=self._timeout,
|
|
135
|
+
)
|
|
136
|
+
except requests.exceptions.Timeout:
|
|
137
|
+
raise AgentBerlinConnectionError(f"Request timed out after {self._timeout}s")
|
|
138
|
+
except requests.exceptions.ConnectionError as e:
|
|
139
|
+
raise AgentBerlinConnectionError(f"Connection error: {e}")
|
|
140
|
+
except requests.exceptions.RequestException as e:
|
|
141
|
+
raise AgentBerlinConnectionError(f"Request failed: {e}")
|
|
142
|
+
|
|
143
|
+
return self._handle_response(response)
|
|
144
|
+
|
|
145
|
+
def _handle_response(self, response: requests.Response) -> Dict[str, Any]:
|
|
146
|
+
"""Handle response and raise appropriate exceptions."""
|
|
147
|
+
# Success
|
|
148
|
+
if response.ok:
|
|
149
|
+
if response.content:
|
|
150
|
+
return response.json() # type: ignore[no-any-return]
|
|
151
|
+
return {}
|
|
152
|
+
|
|
153
|
+
# Parse error response
|
|
154
|
+
try:
|
|
155
|
+
error_data = response.json()
|
|
156
|
+
message = error_data.get("message", error_data.get("error", "Unknown error"))
|
|
157
|
+
error_code = error_data.get("code")
|
|
158
|
+
except Exception:
|
|
159
|
+
message = response.text or f"HTTP {response.status_code}"
|
|
160
|
+
error_code = None
|
|
161
|
+
error_data = {}
|
|
162
|
+
|
|
163
|
+
# Map status codes to exceptions
|
|
164
|
+
if response.status_code == 401:
|
|
165
|
+
raise AgentBerlinAuthenticationError(message, details=error_data)
|
|
166
|
+
elif response.status_code == 404:
|
|
167
|
+
raise AgentBerlinNotFoundError(
|
|
168
|
+
message,
|
|
169
|
+
status_code=404,
|
|
170
|
+
error_code=error_code,
|
|
171
|
+
details=error_data,
|
|
172
|
+
)
|
|
173
|
+
elif response.status_code == 429:
|
|
174
|
+
retry_after = response.headers.get("Retry-After")
|
|
175
|
+
raise AgentBerlinRateLimitError(
|
|
176
|
+
message,
|
|
177
|
+
retry_after=int(retry_after) if retry_after else None,
|
|
178
|
+
details=error_data,
|
|
179
|
+
)
|
|
180
|
+
elif response.status_code >= 500:
|
|
181
|
+
raise AgentBerlinServerError(
|
|
182
|
+
message,
|
|
183
|
+
status_code=response.status_code,
|
|
184
|
+
error_code=error_code,
|
|
185
|
+
details=error_data,
|
|
186
|
+
)
|
|
187
|
+
else:
|
|
188
|
+
raise AgentBerlinAPIError(
|
|
189
|
+
message,
|
|
190
|
+
status_code=response.status_code,
|
|
191
|
+
error_code=error_code,
|
|
192
|
+
details=error_data,
|
|
193
|
+
)
|
agentberlin/client.py
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"""Main client class for Agent Berlin SDK.
|
|
2
|
+
|
|
3
|
+
Example:
|
|
4
|
+
from agentberlin import AgentBerlin
|
|
5
|
+
|
|
6
|
+
client = AgentBerlin() # Uses AGENTBERLIN_TOKEN env var
|
|
7
|
+
|
|
8
|
+
# Get analytics
|
|
9
|
+
analytics = client.analytics.get(project_domain="example.com")
|
|
10
|
+
print(f"Traffic: {analytics.traffic.total_sessions}")
|
|
11
|
+
|
|
12
|
+
# Search pages
|
|
13
|
+
pages = client.pages.search(project_domain="example.com", query="SEO tips")
|
|
14
|
+
for page in pages.pages:
|
|
15
|
+
print(f" - {page.title}: {page.url}")
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
import os
|
|
19
|
+
from typing import Optional
|
|
20
|
+
|
|
21
|
+
from ._http import HTTPClient
|
|
22
|
+
from .config import DEFAULT_BASE_URL, DEFAULT_TIMEOUT, Config
|
|
23
|
+
from .exceptions import AgentBerlinAuthenticationError
|
|
24
|
+
from .resources.analytics import AnalyticsResource
|
|
25
|
+
from .resources.brand import BrandResource
|
|
26
|
+
from .resources.keywords import KeywordsResource
|
|
27
|
+
from .resources.pages import PagesResource
|
|
28
|
+
from .resources.serp import SERPResource
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class AgentBerlin:
|
|
32
|
+
"""Client for the Agent Berlin API.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
token: API token. If not provided, reads from AGENTBERLIN_TOKEN env var.
|
|
36
|
+
base_url: Base URL for the API. Defaults to https://backend.agentberlin.ai/sdk
|
|
37
|
+
timeout: Request timeout in seconds. Defaults to 30.
|
|
38
|
+
|
|
39
|
+
Raises:
|
|
40
|
+
AgentBerlinAuthenticationError: If no token is provided or found in env.
|
|
41
|
+
|
|
42
|
+
Attributes:
|
|
43
|
+
analytics: Analytics resource for fetching analytics data.
|
|
44
|
+
pages: Pages resource for searching and getting page details.
|
|
45
|
+
keywords: Keywords resource for semantic keyword search.
|
|
46
|
+
brand: Brand resource for managing brand profiles.
|
|
47
|
+
serp: SERP resource for fetching Google search results.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
def __init__(
|
|
51
|
+
self,
|
|
52
|
+
token: Optional[str] = None,
|
|
53
|
+
base_url: Optional[str] = None,
|
|
54
|
+
timeout: int = DEFAULT_TIMEOUT,
|
|
55
|
+
) -> None:
|
|
56
|
+
# Get token from parameter or environment
|
|
57
|
+
self._token = token or os.environ.get("AGENTBERLIN_TOKEN")
|
|
58
|
+
if not self._token:
|
|
59
|
+
raise AgentBerlinAuthenticationError(
|
|
60
|
+
"No API token provided. Set AGENTBERLIN_TOKEN environment variable "
|
|
61
|
+
"or pass token parameter to AgentBerlin()."
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
# Initialize config
|
|
65
|
+
self._config = Config(
|
|
66
|
+
base_url=base_url or DEFAULT_BASE_URL,
|
|
67
|
+
timeout=timeout,
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
# Initialize HTTP client
|
|
71
|
+
self._http = HTTPClient(
|
|
72
|
+
token=self._token,
|
|
73
|
+
base_url=self._config.base_url,
|
|
74
|
+
timeout=self._config.timeout,
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
# Initialize resources
|
|
78
|
+
self.analytics = AnalyticsResource(self._http)
|
|
79
|
+
self.pages = PagesResource(self._http)
|
|
80
|
+
self.keywords = KeywordsResource(self._http)
|
|
81
|
+
self.brand = BrandResource(self._http)
|
|
82
|
+
self.serp = SERPResource(self._http)
|
|
83
|
+
|
|
84
|
+
def __repr__(self) -> str:
|
|
85
|
+
return f"AgentBerlin(base_url='{self._config.base_url}')"
|
agentberlin/config.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Configuration for the Agent Berlin SDK."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
DEFAULT_BASE_URL = "https://backend.agentberlin.ai/sdk"
|
|
6
|
+
DEFAULT_TIMEOUT = 30
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass
|
|
10
|
+
class Config:
|
|
11
|
+
"""SDK configuration.
|
|
12
|
+
|
|
13
|
+
Attributes:
|
|
14
|
+
base_url: Base URL for the Agent Berlin API.
|
|
15
|
+
timeout: Request timeout in seconds.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
base_url: str = DEFAULT_BASE_URL
|
|
19
|
+
timeout: int = DEFAULT_TIMEOUT
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"""Custom exceptions for the Agent Berlin SDK.
|
|
2
|
+
|
|
3
|
+
Exception hierarchy:
|
|
4
|
+
AgentBerlinError (base)
|
|
5
|
+
├── AgentBerlinAuthenticationError - Token missing or invalid
|
|
6
|
+
├── AgentBerlinAPIError - API returned an error
|
|
7
|
+
│ ├── AgentBerlinNotFoundError - Resource not found (404)
|
|
8
|
+
│ ├── AgentBerlinRateLimitError - Rate limit exceeded (429)
|
|
9
|
+
│ └── AgentBerlinServerError - Server error (5xx)
|
|
10
|
+
├── AgentBerlinValidationError - Invalid parameters
|
|
11
|
+
└── AgentBerlinConnectionError - Network/connection issues
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from typing import Any, Dict, Optional
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class AgentBerlinError(Exception):
|
|
18
|
+
"""Base exception for all Agent Berlin SDK errors."""
|
|
19
|
+
|
|
20
|
+
def __init__(self, message: str, details: Optional[Dict[str, Any]] = None) -> None:
|
|
21
|
+
super().__init__(message)
|
|
22
|
+
self.message = message
|
|
23
|
+
self.details = details or {}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class AgentBerlinAuthenticationError(AgentBerlinError):
|
|
27
|
+
"""Raised when authentication fails or token is missing."""
|
|
28
|
+
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class AgentBerlinAPIError(AgentBerlinError):
|
|
33
|
+
"""Raised when the API returns an error response."""
|
|
34
|
+
|
|
35
|
+
def __init__(
|
|
36
|
+
self,
|
|
37
|
+
message: str,
|
|
38
|
+
status_code: int,
|
|
39
|
+
error_code: Optional[str] = None,
|
|
40
|
+
details: Optional[Dict[str, Any]] = None,
|
|
41
|
+
) -> None:
|
|
42
|
+
super().__init__(message, details)
|
|
43
|
+
self.status_code = status_code
|
|
44
|
+
self.error_code = error_code
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class AgentBerlinNotFoundError(AgentBerlinAPIError):
|
|
48
|
+
"""Raised when a requested resource is not found (404)."""
|
|
49
|
+
|
|
50
|
+
pass
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class AgentBerlinRateLimitError(AgentBerlinAPIError):
|
|
54
|
+
"""Raised when rate limit is exceeded (429)."""
|
|
55
|
+
|
|
56
|
+
def __init__(
|
|
57
|
+
self,
|
|
58
|
+
message: str,
|
|
59
|
+
retry_after: Optional[int] = None,
|
|
60
|
+
details: Optional[Dict[str, Any]] = None,
|
|
61
|
+
) -> None:
|
|
62
|
+
super().__init__(message, status_code=429, details=details)
|
|
63
|
+
self.retry_after = retry_after
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class AgentBerlinServerError(AgentBerlinAPIError):
|
|
67
|
+
"""Raised when the server returns a 5xx error."""
|
|
68
|
+
|
|
69
|
+
pass
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class AgentBerlinValidationError(AgentBerlinError):
|
|
73
|
+
"""Raised when request parameters fail validation."""
|
|
74
|
+
|
|
75
|
+
def __init__(
|
|
76
|
+
self,
|
|
77
|
+
message: str,
|
|
78
|
+
field: Optional[str] = None,
|
|
79
|
+
details: Optional[Dict[str, Any]] = None,
|
|
80
|
+
) -> None:
|
|
81
|
+
super().__init__(message, details)
|
|
82
|
+
self.field = field
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class AgentBerlinConnectionError(AgentBerlinError):
|
|
86
|
+
"""Raised when there's a network or connection error."""
|
|
87
|
+
|
|
88
|
+
pass
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""Pydantic models for Agent Berlin API responses."""
|
|
2
|
+
|
|
3
|
+
from .analytics import (
|
|
4
|
+
AnalyticsResponse,
|
|
5
|
+
ChannelBreakdown,
|
|
6
|
+
CompetitorSummary,
|
|
7
|
+
DailyTraffic,
|
|
8
|
+
DataRange,
|
|
9
|
+
TopicSummary,
|
|
10
|
+
TrafficData,
|
|
11
|
+
VisibilityData,
|
|
12
|
+
VisibilityPoint,
|
|
13
|
+
)
|
|
14
|
+
from .brand import BrandProfileResponse, BrandProfileUpdateResponse
|
|
15
|
+
from .search import (
|
|
16
|
+
KeywordResult,
|
|
17
|
+
KeywordSearchResponse,
|
|
18
|
+
PageDetailResponse,
|
|
19
|
+
PageLink,
|
|
20
|
+
PageLinksDetail,
|
|
21
|
+
PageResult,
|
|
22
|
+
PageSearchResponse,
|
|
23
|
+
PageTopicInfo,
|
|
24
|
+
)
|
|
25
|
+
from .serp import SERPResponse, SERPResult
|
|
26
|
+
|
|
27
|
+
__all__ = [
|
|
28
|
+
# Analytics
|
|
29
|
+
"AnalyticsResponse",
|
|
30
|
+
"VisibilityData",
|
|
31
|
+
"VisibilityPoint",
|
|
32
|
+
"TrafficData",
|
|
33
|
+
"ChannelBreakdown",
|
|
34
|
+
"DailyTraffic",
|
|
35
|
+
"TopicSummary",
|
|
36
|
+
"CompetitorSummary",
|
|
37
|
+
"DataRange",
|
|
38
|
+
# Search
|
|
39
|
+
"PageSearchResponse",
|
|
40
|
+
"PageResult",
|
|
41
|
+
"KeywordSearchResponse",
|
|
42
|
+
"KeywordResult",
|
|
43
|
+
"PageDetailResponse",
|
|
44
|
+
"PageLinksDetail",
|
|
45
|
+
"PageLink",
|
|
46
|
+
"PageTopicInfo",
|
|
47
|
+
# Brand
|
|
48
|
+
"BrandProfileResponse",
|
|
49
|
+
"BrandProfileUpdateResponse",
|
|
50
|
+
# SERP
|
|
51
|
+
"SERPResponse",
|
|
52
|
+
"SERPResult",
|
|
53
|
+
]
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"""Pydantic models for analytics API responses."""
|
|
2
|
+
|
|
3
|
+
from typing import List
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, Field
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class VisibilityPoint(BaseModel):
|
|
9
|
+
"""Single point in visibility history."""
|
|
10
|
+
|
|
11
|
+
date: str
|
|
12
|
+
percentage: float
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class VisibilityData(BaseModel):
|
|
16
|
+
"""Visibility metrics for a domain."""
|
|
17
|
+
|
|
18
|
+
current_percentage: float
|
|
19
|
+
ranking_stability: float
|
|
20
|
+
share_of_voice: float
|
|
21
|
+
history: List[VisibilityPoint] = Field(default_factory=list)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class ChannelBreakdown(BaseModel):
|
|
25
|
+
"""Traffic breakdown by channel."""
|
|
26
|
+
|
|
27
|
+
direct: int = 0
|
|
28
|
+
organic_search: int = 0
|
|
29
|
+
referral: int = 0
|
|
30
|
+
organic_social: int = 0
|
|
31
|
+
llm: int = 0
|
|
32
|
+
other: int = 0
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class DailyTraffic(BaseModel):
|
|
36
|
+
"""Daily traffic data point."""
|
|
37
|
+
|
|
38
|
+
date: str
|
|
39
|
+
sessions: int
|
|
40
|
+
llm_sessions: int
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class TrafficData(BaseModel):
|
|
44
|
+
"""Traffic metrics for a domain."""
|
|
45
|
+
|
|
46
|
+
total_sessions: int
|
|
47
|
+
llm_sessions: int
|
|
48
|
+
channel_breakdown: ChannelBreakdown
|
|
49
|
+
daily_trend: List[DailyTraffic] = Field(default_factory=list)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class TopicSummary(BaseModel):
|
|
53
|
+
"""Summary of a topic's performance."""
|
|
54
|
+
|
|
55
|
+
name: str
|
|
56
|
+
appearances: int
|
|
57
|
+
avg_position: float
|
|
58
|
+
topical_authority: float
|
|
59
|
+
trend: str # "up", "down", "stable"
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class CompetitorSummary(BaseModel):
|
|
63
|
+
"""Summary of competitor visibility."""
|
|
64
|
+
|
|
65
|
+
name: str
|
|
66
|
+
visibility: float
|
|
67
|
+
share_of_voice: float
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class DataRange(BaseModel):
|
|
71
|
+
"""Date range for the analytics data."""
|
|
72
|
+
|
|
73
|
+
start: str
|
|
74
|
+
end: str
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class AnalyticsResponse(BaseModel):
|
|
78
|
+
"""Complete analytics response for a domain."""
|
|
79
|
+
|
|
80
|
+
domain: str
|
|
81
|
+
domain_authority: int
|
|
82
|
+
visibility: VisibilityData
|
|
83
|
+
traffic: TrafficData
|
|
84
|
+
topics: List[TopicSummary] = Field(default_factory=list)
|
|
85
|
+
competitors: List[CompetitorSummary] = Field(default_factory=list)
|
|
86
|
+
data_range: DataRange
|
|
87
|
+
last_updated: str
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""Pydantic models for brand profile API responses."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, List, Optional
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, Field
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class BrandProfileResponse(BaseModel):
|
|
9
|
+
"""Brand profile configuration for a project."""
|
|
10
|
+
|
|
11
|
+
domain: str
|
|
12
|
+
name: str
|
|
13
|
+
context: str
|
|
14
|
+
search_analysis_context: Optional[str] = None
|
|
15
|
+
domain_authority: int
|
|
16
|
+
competitors: List[str] = Field(default_factory=list)
|
|
17
|
+
industries: List[str] = Field(default_factory=list)
|
|
18
|
+
business_models: List[str] = Field(default_factory=list)
|
|
19
|
+
company_size: str
|
|
20
|
+
target_customer_segments: List[str] = Field(default_factory=list)
|
|
21
|
+
geographies: List[str] = Field(default_factory=list)
|
|
22
|
+
personas: List[str] = Field(default_factory=list)
|
|
23
|
+
sitemaps: List[str] = Field(default_factory=list)
|
|
24
|
+
profile_urls: List[str] = Field(default_factory=list)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class BrandProfileUpdateField(BaseModel):
|
|
28
|
+
"""Updated field information."""
|
|
29
|
+
|
|
30
|
+
field: str
|
|
31
|
+
value: Any
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class BrandProfileUpdateResponse(BaseModel):
|
|
35
|
+
"""Response for brand profile update."""
|
|
36
|
+
|
|
37
|
+
success: bool
|
|
38
|
+
domain: str
|
|
39
|
+
mode: str
|
|
40
|
+
updated: BrandProfileUpdateField
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"""Pydantic models for search API responses (pages, keywords)."""
|
|
2
|
+
|
|
3
|
+
from typing import List, Optional
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, Field
|
|
6
|
+
|
|
7
|
+
# Page search models
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class PageResult(BaseModel):
|
|
11
|
+
"""Single page in search results."""
|
|
12
|
+
|
|
13
|
+
url: str
|
|
14
|
+
title: str
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class PageSearchResponse(BaseModel):
|
|
18
|
+
"""Response for page search."""
|
|
19
|
+
|
|
20
|
+
pages: List[PageResult] = Field(default_factory=list)
|
|
21
|
+
total: int
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# Keyword search models
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class KeywordResult(BaseModel):
|
|
28
|
+
"""Single keyword with metadata."""
|
|
29
|
+
|
|
30
|
+
keyword: str
|
|
31
|
+
volume: Optional[int] = None
|
|
32
|
+
difficulty: Optional[int] = None # 0-100
|
|
33
|
+
cpc: Optional[float] = None
|
|
34
|
+
intent: Optional[str] = None # informational, commercial, transactional, navigational
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class KeywordSearchResponse(BaseModel):
|
|
38
|
+
"""Response for keyword search."""
|
|
39
|
+
|
|
40
|
+
keywords: List[KeywordResult] = Field(default_factory=list)
|
|
41
|
+
total: int
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
# Get page models
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class PageLink(BaseModel):
|
|
48
|
+
"""Link in page details."""
|
|
49
|
+
|
|
50
|
+
source_url: str
|
|
51
|
+
target_url: str
|
|
52
|
+
anchor_text: Optional[str] = None
|
|
53
|
+
domain_type: str # "internal" or "external"
|
|
54
|
+
source_title: Optional[str] = None
|
|
55
|
+
target_title: Optional[str] = None
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class PageLinksDetail(BaseModel):
|
|
59
|
+
"""Links for a page."""
|
|
60
|
+
|
|
61
|
+
inlinks: List[PageLink] = Field(default_factory=list)
|
|
62
|
+
outlinks: List[PageLink] = Field(default_factory=list)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class PageTopicInfo(BaseModel):
|
|
66
|
+
"""Topic classification for a page."""
|
|
67
|
+
|
|
68
|
+
topics: List[str] = Field(default_factory=list)
|
|
69
|
+
topic_scores: List[float] = Field(default_factory=list)
|
|
70
|
+
page_type: Optional[str] = None # "pillar" or "landing"
|
|
71
|
+
assigned_topic: Optional[str] = None
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class PageDetailResponse(BaseModel):
|
|
75
|
+
"""Detailed page information."""
|
|
76
|
+
|
|
77
|
+
url: str
|
|
78
|
+
title: Optional[str] = None
|
|
79
|
+
meta_description: Optional[str] = None
|
|
80
|
+
h1: Optional[str] = None
|
|
81
|
+
domain: Optional[str] = None
|
|
82
|
+
links: Optional[PageLinksDetail] = None
|
|
83
|
+
topic_info: Optional[PageTopicInfo] = None
|
|
84
|
+
content_preview: Optional[str] = None
|
|
85
|
+
content_length: int = 0
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""Pydantic models for SERP API responses."""
|
|
2
|
+
|
|
3
|
+
from typing import List
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, Field
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class SERPResult(BaseModel):
|
|
9
|
+
"""Single search result."""
|
|
10
|
+
|
|
11
|
+
title: str
|
|
12
|
+
url: str
|
|
13
|
+
snippet: str
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class SERPResponse(BaseModel):
|
|
17
|
+
"""Response for SERP fetch."""
|
|
18
|
+
|
|
19
|
+
query: str
|
|
20
|
+
results: List[SERPResult] = Field(default_factory=list)
|
|
21
|
+
total: int
|
agentberlin/py.typed
ADDED
|
File without changes
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Resource classes for Agent Berlin SDK."""
|
|
2
|
+
|
|
3
|
+
from .analytics import AnalyticsResource
|
|
4
|
+
from .brand import BrandResource
|
|
5
|
+
from .keywords import KeywordsResource
|
|
6
|
+
from .pages import PagesResource
|
|
7
|
+
from .serp import SERPResource
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"AnalyticsResource",
|
|
11
|
+
"PagesResource",
|
|
12
|
+
"KeywordsResource",
|
|
13
|
+
"BrandResource",
|
|
14
|
+
"SERPResource",
|
|
15
|
+
]
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""Analytics resource for Agent Berlin SDK."""
|
|
2
|
+
|
|
3
|
+
from .._http import HTTPClient
|
|
4
|
+
from ..models.analytics import AnalyticsResponse
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class AnalyticsResource:
|
|
8
|
+
"""Resource for analytics operations.
|
|
9
|
+
|
|
10
|
+
Example:
|
|
11
|
+
analytics = client.analytics.get(project_domain="example.com")
|
|
12
|
+
print(f"Visibility: {analytics.visibility.current_percentage}%")
|
|
13
|
+
print(f"LLM Sessions: {analytics.traffic.llm_sessions}")
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
def __init__(self, http: HTTPClient) -> None:
|
|
17
|
+
self._http = http
|
|
18
|
+
|
|
19
|
+
def get(self, project_domain: str) -> AnalyticsResponse:
|
|
20
|
+
"""Get analytics data for a project.
|
|
21
|
+
|
|
22
|
+
Fetch analytics dashboard data including visibility, traffic,
|
|
23
|
+
topics, and competitors.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
project_domain: The domain of the project (e.g., 'example.com').
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
AnalyticsResponse with comprehensive analytics data.
|
|
30
|
+
|
|
31
|
+
Raises:
|
|
32
|
+
AgentBerlinValidationError: If project_domain is empty.
|
|
33
|
+
AgentBerlinNotFoundError: If the domain doesn't exist.
|
|
34
|
+
AgentBerlinAPIError: If the API returns an error.
|
|
35
|
+
"""
|
|
36
|
+
data = self._http.post(
|
|
37
|
+
"/analytics",
|
|
38
|
+
json={"project_domain": project_domain},
|
|
39
|
+
)
|
|
40
|
+
return AnalyticsResponse.model_validate(data)
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"""Brand resource for Agent Berlin SDK."""
|
|
2
|
+
|
|
3
|
+
from typing import Literal, Optional
|
|
4
|
+
|
|
5
|
+
from .._http import HTTPClient
|
|
6
|
+
from ..models.brand import BrandProfileResponse, BrandProfileUpdateResponse
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class BrandResource:
|
|
10
|
+
"""Resource for brand profile operations.
|
|
11
|
+
|
|
12
|
+
Example:
|
|
13
|
+
# Get brand profile
|
|
14
|
+
profile = client.brand.get_profile(project_domain="example.com")
|
|
15
|
+
print(f"Domain Authority: {profile.domain_authority}")
|
|
16
|
+
print(f"Competitors: {profile.competitors}")
|
|
17
|
+
|
|
18
|
+
# Update brand profile
|
|
19
|
+
client.brand.update_profile(
|
|
20
|
+
project_domain="example.com",
|
|
21
|
+
field="competitors",
|
|
22
|
+
value="competitor.com",
|
|
23
|
+
mode="add"
|
|
24
|
+
)
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def __init__(self, http: HTTPClient) -> None:
|
|
28
|
+
self._http = http
|
|
29
|
+
|
|
30
|
+
def get_profile(self, project_domain: str) -> BrandProfileResponse:
|
|
31
|
+
"""Get the brand profile configuration for a project.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
project_domain: The domain of the project (e.g., 'example.com').
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
BrandProfileResponse with brand configuration.
|
|
38
|
+
"""
|
|
39
|
+
data = self._http.post(
|
|
40
|
+
"/brand/profile",
|
|
41
|
+
json={"project_domain": project_domain},
|
|
42
|
+
)
|
|
43
|
+
return BrandProfileResponse.model_validate(data)
|
|
44
|
+
|
|
45
|
+
def update_profile(
|
|
46
|
+
self,
|
|
47
|
+
project_domain: str,
|
|
48
|
+
field: Literal[
|
|
49
|
+
"name",
|
|
50
|
+
"context",
|
|
51
|
+
"competitors",
|
|
52
|
+
"industries",
|
|
53
|
+
"business_models",
|
|
54
|
+
"company_size",
|
|
55
|
+
"target_segments",
|
|
56
|
+
"geographies",
|
|
57
|
+
"personas",
|
|
58
|
+
],
|
|
59
|
+
value: str,
|
|
60
|
+
*,
|
|
61
|
+
mode: Literal["add", "set"] = "add",
|
|
62
|
+
) -> BrandProfileUpdateResponse:
|
|
63
|
+
"""Update a specific field in the brand profile.
|
|
64
|
+
|
|
65
|
+
For array fields (competitors, industries, etc.), you can either
|
|
66
|
+
add new values to existing ones or set (replace) all values.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
project_domain: The domain of the project (e.g., 'example.com').
|
|
70
|
+
field: The field to update.
|
|
71
|
+
value: The new value. For array fields, provide comma-separated values.
|
|
72
|
+
mode: For array fields: 'add' adds to existing, 'set' replaces all.
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
BrandProfileUpdateResponse with update confirmation.
|
|
76
|
+
|
|
77
|
+
Note:
|
|
78
|
+
Valid company_size values: solo, early_startup, startup, smb, mid_market, enterprise
|
|
79
|
+
"""
|
|
80
|
+
payload: dict[str, object] = {
|
|
81
|
+
"project_domain": project_domain,
|
|
82
|
+
"field": field,
|
|
83
|
+
"value": value,
|
|
84
|
+
"mode": mode,
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
data = self._http.post("/brand/profile/update", json=payload)
|
|
88
|
+
return BrandProfileUpdateResponse.model_validate(data)
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""Keywords resource for Agent Berlin SDK."""
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
from .._http import HTTPClient
|
|
6
|
+
from ..models.search import KeywordSearchResponse
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class KeywordsResource:
|
|
10
|
+
"""Resource for keyword operations.
|
|
11
|
+
|
|
12
|
+
Example:
|
|
13
|
+
results = client.keywords.search(
|
|
14
|
+
project_domain="example.com",
|
|
15
|
+
query="digital marketing",
|
|
16
|
+
limit=20
|
|
17
|
+
)
|
|
18
|
+
for kw in results.keywords:
|
|
19
|
+
print(f"{kw.keyword}: volume={kw.volume}, difficulty={kw.difficulty}")
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def __init__(self, http: HTTPClient) -> None:
|
|
23
|
+
self._http = http
|
|
24
|
+
|
|
25
|
+
def search(
|
|
26
|
+
self,
|
|
27
|
+
project_domain: str,
|
|
28
|
+
query: str,
|
|
29
|
+
*,
|
|
30
|
+
limit: Optional[int] = None,
|
|
31
|
+
) -> KeywordSearchResponse:
|
|
32
|
+
"""Search for keywords by semantic query.
|
|
33
|
+
|
|
34
|
+
Returns relevant keywords with metadata including search volume,
|
|
35
|
+
difficulty, CPC, and intent classification.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
project_domain: Your project's domain (e.g., 'example.com').
|
|
39
|
+
query: Search query for semantic matching.
|
|
40
|
+
limit: Maximum results (default: 10, max: 50).
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
KeywordSearchResponse with matching keywords and metadata.
|
|
44
|
+
"""
|
|
45
|
+
payload: dict[str, object] = {
|
|
46
|
+
"project_domain": project_domain,
|
|
47
|
+
"query": query,
|
|
48
|
+
}
|
|
49
|
+
if limit is not None:
|
|
50
|
+
payload["limit"] = limit
|
|
51
|
+
|
|
52
|
+
data = self._http.post("/keywords/search", json=payload)
|
|
53
|
+
return KeywordSearchResponse.model_validate(data)
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"""Pages resource for Agent Berlin SDK."""
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
from .._http import HTTPClient
|
|
6
|
+
from ..models.search import PageDetailResponse, PageSearchResponse
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class PagesResource:
|
|
10
|
+
"""Resource for page operations.
|
|
11
|
+
|
|
12
|
+
Example:
|
|
13
|
+
# Search for pages
|
|
14
|
+
results = client.pages.search(
|
|
15
|
+
project_domain="example.com",
|
|
16
|
+
query="SEO best practices",
|
|
17
|
+
limit=20
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
# Get page details
|
|
21
|
+
page = client.pages.get(
|
|
22
|
+
project_domain="example.com",
|
|
23
|
+
url="https://example.com/blog/seo-tips"
|
|
24
|
+
)
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def __init__(self, http: HTTPClient) -> None:
|
|
28
|
+
self._http = http
|
|
29
|
+
|
|
30
|
+
def search(
|
|
31
|
+
self,
|
|
32
|
+
project_domain: str,
|
|
33
|
+
query: str,
|
|
34
|
+
*,
|
|
35
|
+
domain: Optional[str] = None,
|
|
36
|
+
limit: Optional[int] = None,
|
|
37
|
+
status_code: Optional[str] = None,
|
|
38
|
+
topic: Optional[str] = None,
|
|
39
|
+
page_type: Optional[str] = None,
|
|
40
|
+
) -> PageSearchResponse:
|
|
41
|
+
"""Search for pages by semantic query.
|
|
42
|
+
|
|
43
|
+
Can search your brand's pages, competitor pages, or all indexed pages.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
project_domain: Your project's domain (e.g., 'example.com').
|
|
47
|
+
query: Search query for semantic matching.
|
|
48
|
+
domain: Optional filter to a specific domain.
|
|
49
|
+
limit: Maximum results (default: 10, max: 50).
|
|
50
|
+
status_code: Filter by HTTP status ('200', '404', 'error', 'redirect', 'success').
|
|
51
|
+
topic: Filter by topic name.
|
|
52
|
+
page_type: Filter by page type ('pillar' or 'landing').
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
PageSearchResponse with matching pages.
|
|
56
|
+
"""
|
|
57
|
+
payload: dict[str, object] = {
|
|
58
|
+
"project_domain": project_domain,
|
|
59
|
+
"query": query,
|
|
60
|
+
}
|
|
61
|
+
if domain is not None:
|
|
62
|
+
payload["domain"] = domain
|
|
63
|
+
if limit is not None:
|
|
64
|
+
payload["limit"] = limit
|
|
65
|
+
if status_code is not None:
|
|
66
|
+
payload["status_code"] = status_code
|
|
67
|
+
if topic is not None:
|
|
68
|
+
payload["topic"] = topic
|
|
69
|
+
if page_type is not None:
|
|
70
|
+
payload["page_type"] = page_type
|
|
71
|
+
|
|
72
|
+
data = self._http.post("/pages/search", json=payload)
|
|
73
|
+
return PageSearchResponse.model_validate(data)
|
|
74
|
+
|
|
75
|
+
def get(
|
|
76
|
+
self,
|
|
77
|
+
project_domain: str,
|
|
78
|
+
url: str,
|
|
79
|
+
*,
|
|
80
|
+
content_length: Optional[int] = None,
|
|
81
|
+
) -> PageDetailResponse:
|
|
82
|
+
"""Get detailed information about a specific page.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
project_domain: Your project's domain (e.g., 'example.com').
|
|
86
|
+
url: The page URL to look up.
|
|
87
|
+
content_length: Max characters of content to return (default: 0).
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
PageDetailResponse with page metadata, links, and topic info.
|
|
91
|
+
"""
|
|
92
|
+
payload: dict[str, object] = {
|
|
93
|
+
"project_domain": project_domain,
|
|
94
|
+
"url": url,
|
|
95
|
+
}
|
|
96
|
+
if content_length is not None:
|
|
97
|
+
payload["content_length"] = content_length
|
|
98
|
+
|
|
99
|
+
data = self._http.post("/pages/get", json=payload)
|
|
100
|
+
return PageDetailResponse.model_validate(data)
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"""SERP resource for Agent Berlin SDK."""
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
from .._http import HTTPClient
|
|
6
|
+
from ..models.serp import SERPResponse
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class SERPResource:
|
|
10
|
+
"""Resource for SERP (Search Engine Results Page) operations.
|
|
11
|
+
|
|
12
|
+
Example:
|
|
13
|
+
results = client.serp.fetch(
|
|
14
|
+
query="best seo tools",
|
|
15
|
+
max_results=5,
|
|
16
|
+
country="us",
|
|
17
|
+
language="lang_en"
|
|
18
|
+
)
|
|
19
|
+
for result in results.results:
|
|
20
|
+
print(f"{result.title}: {result.url}")
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __init__(self, http: HTTPClient) -> None:
|
|
24
|
+
self._http = http
|
|
25
|
+
|
|
26
|
+
def fetch(
|
|
27
|
+
self,
|
|
28
|
+
query: str,
|
|
29
|
+
*,
|
|
30
|
+
max_results: Optional[int] = None,
|
|
31
|
+
country: Optional[str] = None,
|
|
32
|
+
language: Optional[str] = None,
|
|
33
|
+
) -> SERPResponse:
|
|
34
|
+
"""Fetch Google SERP data for a query.
|
|
35
|
+
|
|
36
|
+
Returns titles, URLs, and snippets of top-ranking pages.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
query: The search query to execute.
|
|
40
|
+
max_results: Number of results (1-10, default: 10).
|
|
41
|
+
country: ISO 3166-1 alpha-2 country code (e.g., 'us', 'gb', 'de').
|
|
42
|
+
language: ISO 639-1 language code prefixed with 'lang_'
|
|
43
|
+
(e.g., 'lang_en', 'lang_de', 'lang_fr').
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
SERPResponse with search results.
|
|
47
|
+
"""
|
|
48
|
+
payload: dict[str, object] = {"query": query}
|
|
49
|
+
if max_results is not None:
|
|
50
|
+
payload["max_results"] = max_results
|
|
51
|
+
if country is not None:
|
|
52
|
+
payload["country"] = country
|
|
53
|
+
if language is not None:
|
|
54
|
+
payload["language"] = language
|
|
55
|
+
|
|
56
|
+
data = self._http.post("/serp/fetch", json=payload)
|
|
57
|
+
return SERPResponse.model_validate(data)
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: agentberlin
|
|
3
|
+
Version: 0.9.0
|
|
4
|
+
Summary: Python SDK for Agent Berlin - AI-powered SEO and AEO automation
|
|
5
|
+
Project-URL: Homepage, https://agentberlin.ai
|
|
6
|
+
Project-URL: Documentation, https://docs.agentberlin.ai/sdk/python
|
|
7
|
+
Project-URL: Repository, https://github.com/boat-builder/berlin
|
|
8
|
+
Author-email: Agent Berlin <support@agentberlin.ai>
|
|
9
|
+
License: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: aeo,ai,analytics,optimization,search,seo
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Typing :: Typed
|
|
21
|
+
Requires-Python: >=3.9
|
|
22
|
+
Requires-Dist: pydantic>=2.0.0
|
|
23
|
+
Requires-Dist: requests>=2.28.0
|
|
24
|
+
Provides-Extra: dev
|
|
25
|
+
Requires-Dist: black>=24.0.0; extra == 'dev'
|
|
26
|
+
Requires-Dist: isort>=5.13.0; extra == 'dev'
|
|
27
|
+
Requires-Dist: mypy>=1.8.0; extra == 'dev'
|
|
28
|
+
Requires-Dist: pytest-cov>=4.1.0; extra == 'dev'
|
|
29
|
+
Requires-Dist: pytest>=8.0.0; extra == 'dev'
|
|
30
|
+
Requires-Dist: responses>=0.25.0; extra == 'dev'
|
|
31
|
+
Requires-Dist: types-requests>=2.31.0; extra == 'dev'
|
|
32
|
+
Description-Content-Type: text/markdown
|
|
33
|
+
|
|
34
|
+
# Agent Berlin Python SDK
|
|
35
|
+
|
|
36
|
+
Official Python SDK for [Agent Berlin](https://agentberlin.ai) - AI-powered SEO and AEO automation.
|
|
37
|
+
|
|
38
|
+
## Installation
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
pip install agentberlin
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Quick Start
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
from agentberlin import AgentBerlin
|
|
48
|
+
|
|
49
|
+
# Set your API token as an environment variable
|
|
50
|
+
# export AGENTBERLIN_TOKEN="your-token-here"
|
|
51
|
+
|
|
52
|
+
client = AgentBerlin()
|
|
53
|
+
|
|
54
|
+
# Get analytics for your project
|
|
55
|
+
analytics = client.analytics.get(project_domain="example.com")
|
|
56
|
+
print(f"Visibility: {analytics.visibility.current_percentage}%")
|
|
57
|
+
print(f"LLM Sessions: {analytics.traffic.llm_sessions}")
|
|
58
|
+
|
|
59
|
+
# Search for pages
|
|
60
|
+
pages = client.pages.search(
|
|
61
|
+
project_domain="example.com",
|
|
62
|
+
query="SEO best practices",
|
|
63
|
+
limit=10
|
|
64
|
+
)
|
|
65
|
+
for page in pages.pages:
|
|
66
|
+
print(f" - {page.title}: {page.url}")
|
|
67
|
+
|
|
68
|
+
# Search for keywords
|
|
69
|
+
keywords = client.keywords.search(
|
|
70
|
+
project_domain="example.com",
|
|
71
|
+
query="digital marketing"
|
|
72
|
+
)
|
|
73
|
+
for kw in keywords.keywords:
|
|
74
|
+
print(f" - {kw.keyword} (volume: {kw.volume})")
|
|
75
|
+
|
|
76
|
+
# Get page details
|
|
77
|
+
page = client.pages.get(
|
|
78
|
+
project_domain="example.com",
|
|
79
|
+
url="https://example.com/blog/seo-tips"
|
|
80
|
+
)
|
|
81
|
+
print(f"Title: {page.title}")
|
|
82
|
+
print(f"H1: {page.h1}")
|
|
83
|
+
|
|
84
|
+
# Get brand profile
|
|
85
|
+
profile = client.brand.get_profile(project_domain="example.com")
|
|
86
|
+
print(f"Domain Authority: {profile.domain_authority}")
|
|
87
|
+
|
|
88
|
+
# Update brand profile
|
|
89
|
+
client.brand.update_profile(
|
|
90
|
+
project_domain="example.com",
|
|
91
|
+
field="competitors",
|
|
92
|
+
value="competitor.com",
|
|
93
|
+
mode="add"
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
# Fetch SERP results
|
|
97
|
+
serp = client.serp.fetch(query="best seo tools", max_results=5)
|
|
98
|
+
for result in serp.results:
|
|
99
|
+
print(f" - {result.title}: {result.url}")
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Authentication
|
|
103
|
+
|
|
104
|
+
The SDK requires an API token. Set it as an environment variable:
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
export AGENTBERLIN_TOKEN="your-token-here"
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Or pass it directly:
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
client = AgentBerlin(token="your-token-here")
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Configuration
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
client = AgentBerlin(
|
|
120
|
+
token="your-token", # Optional if AGENTBERLIN_TOKEN env var is set
|
|
121
|
+
base_url="https://...", # Optional, defaults to production API
|
|
122
|
+
timeout=30, # Request timeout in seconds
|
|
123
|
+
)
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Resources
|
|
127
|
+
|
|
128
|
+
### Analytics
|
|
129
|
+
```python
|
|
130
|
+
client.analytics.get(project_domain="example.com")
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Pages
|
|
134
|
+
```python
|
|
135
|
+
client.pages.search(project_domain, query, domain=None, limit=10, status_code=None, topic=None, page_type=None)
|
|
136
|
+
client.pages.get(project_domain, url, content_length=0)
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Keywords
|
|
140
|
+
```python
|
|
141
|
+
client.keywords.search(project_domain, query, limit=10)
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Brand
|
|
145
|
+
```python
|
|
146
|
+
client.brand.get_profile(project_domain)
|
|
147
|
+
client.brand.update_profile(project_domain, field, value, mode="add")
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### SERP
|
|
151
|
+
```python
|
|
152
|
+
client.serp.fetch(query, max_results=10, country=None, language=None)
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## Error Handling
|
|
156
|
+
|
|
157
|
+
```python
|
|
158
|
+
from agentberlin import AgentBerlin
|
|
159
|
+
from agentberlin.exceptions import (
|
|
160
|
+
AgentBerlinError,
|
|
161
|
+
AgentBerlinAuthenticationError,
|
|
162
|
+
AgentBerlinNotFoundError,
|
|
163
|
+
AgentBerlinRateLimitError,
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
client = AgentBerlin()
|
|
167
|
+
|
|
168
|
+
try:
|
|
169
|
+
analytics = client.analytics.get(project_domain="example.com")
|
|
170
|
+
except AgentBerlinAuthenticationError:
|
|
171
|
+
print("Invalid or missing API token")
|
|
172
|
+
except AgentBerlinNotFoundError:
|
|
173
|
+
print("Domain not found")
|
|
174
|
+
except AgentBerlinRateLimitError as e:
|
|
175
|
+
print(f"Rate limited. Retry after {e.retry_after} seconds")
|
|
176
|
+
except AgentBerlinError as e:
|
|
177
|
+
print(f"API error: {e.message}")
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## License
|
|
181
|
+
|
|
182
|
+
MIT License - see [LICENSE](LICENSE) for details.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
agentberlin/__init__.py,sha256=MMlz6BOMuAXTErssccHYbSP4AFAcA773hdcs-K83HxM,1327
|
|
2
|
+
agentberlin/_http.py,sha256=_ZoYl5ro23Yub_5HTtS4qX-m0RlhFV2-mF2WLcTUgEA,5995
|
|
3
|
+
agentberlin/client.py,sha256=MPy8MzI5yX7kru2_NI8LEXOOXzaLYZlAUuMmGEly9UE,2880
|
|
4
|
+
agentberlin/config.py,sha256=qPGBExHv1sPY9o9CDrhnbHS9EDRPSn-_yg2zL2H6PNo,408
|
|
5
|
+
agentberlin/exceptions.py,sha256=3YCqboljn9LSi0ueGDBR7h9lGKa0RbLD3LOmhqMZ5RU,2561
|
|
6
|
+
agentberlin/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
|
+
agentberlin/models/__init__.py,sha256=dumF4EML3WklZjpzYuqA35FeO97pntf8DZUPu6zNGuo,1100
|
|
8
|
+
agentberlin/models/analytics.py,sha256=7Ot--db2VW5b0qnV04O_ZL5jIUlAnLIO_c0EjsePnXI,1849
|
|
9
|
+
agentberlin/models/brand.py,sha256=UD9NdknaPt3vbFs9DhGzvsrw6WykMBJHKsBVllzAW9A,1131
|
|
10
|
+
agentberlin/models/search.py,sha256=vZp-hR32CwDBagyngxw3N3-i7I4Ko-FrKDvxksEHHg8,2042
|
|
11
|
+
agentberlin/models/serp.py,sha256=QyEdtVvzPpqjRtmxuX7sKtlcBnOhUQTsZPS3cwZuI0M,378
|
|
12
|
+
agentberlin/resources/__init__.py,sha256=5_MjO0SyAxpR7QZkP9nYo5-m_ctsjwgAwxWDSYHyojo,349
|
|
13
|
+
agentberlin/resources/analytics.py,sha256=iVcknPm6gZXt920xhlHy8Pk8T-WL-rYpung2qqrvEQs,1292
|
|
14
|
+
agentberlin/resources/brand.py,sha256=tgV-YY2NS82-GRhBiwhgyvu5jgbMszZ1_wEVSXn5eOo,2752
|
|
15
|
+
agentberlin/resources/keywords.py,sha256=cCluTqCplTom1YAqnZV8C5nJpiByQ1-SR-XtcoCV2sA,1548
|
|
16
|
+
agentberlin/resources/pages.py,sha256=jeZu5JXr-rvGSepqoTsowkMQ1UFj5WBGPMckbPnIB0Q,3132
|
|
17
|
+
agentberlin/resources/serp.py,sha256=MZdAIicsMewHVm3k5JkBBow6PipQrrTKjPi1P4sHz8o,1715
|
|
18
|
+
agentberlin-0.9.0.dist-info/METADATA,sha256=7SebC4-n3E9Qe1WOQmpZa_IKJ4k7a3LQh2gQW97W9pE,4766
|
|
19
|
+
agentberlin-0.9.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
20
|
+
agentberlin-0.9.0.dist-info/licenses/LICENSE,sha256=BX6Yp57z81S2GsPrqvDKIucPL9jG5U3Jaqzn_dxO0wA,1069
|
|
21
|
+
agentberlin-0.9.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Agent Berlin
|
|
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.
|