seoscoreapi 1.6.0__tar.gz → 1.7.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: seoscoreapi
3
- Version: 1.6.0
3
+ Version: 1.7.0
4
4
  Summary: Python client for SEO Score API — audit any URL for SEO issues with one function call
5
5
  Author-email: SEO Score API <info@seoscoreapi.com>
6
6
  License: MIT
@@ -29,10 +29,11 @@ pip install seoscoreapi
29
29
  ## Quick Start
30
30
 
31
31
  ```python
32
- from seoscoreapi import audit, signup
32
+ import os
33
+ from seoscoreapi import audit
33
34
 
34
- # Get a free API key (2 audits/day, no credit card)
35
- key = signup("you@example.com")
35
+ # Get a free API key (2 audits/day, no credit card) at https://seoscoreapi.com/#signup
36
+ key = os.environ["SEO_SCORE_API_KEY"]
36
37
 
37
38
  # Run an audit
38
39
  result = audit("https://example.com", api_key=key)
@@ -43,9 +44,10 @@ print(f"Score: {result['score']}/100 ({result['grade']})")
43
44
 
44
45
  | Function | Description |
45
46
  |---|---|
46
- | `signup(email)` | Get a free API key |
47
- | `audit(url, api_key)` | Run SEO audit on a URL |
48
- | `batch_audit(urls, api_key)` | Audit up to 10 URLs in one call (paid) |
47
+ | `signup(email)` | Start signup: the API emails a 6-digit code. Returns the API's response (`{"status": "verification_required", ...}`), not a key |
48
+ | `verify(email, code)` | Finish signup with the emailed code. Returns the API key (shown only once) |
49
+ | `audit(url, api_key, cwv=False)` | Run SEO audit on a URL. `cwv=True` adds measured Core Web Vitals (slower, see below) |
50
+ | `batch_audit(urls, api_key, cwv=False)` | Audit up to 10 URLs in one call (paid). `cwv=True` adds Core Web Vitals to each |
49
51
  | `compare(urls, api_key)` | Compare 2–5 URLs with a structured diff (Basic+) |
50
52
  | `competitive_audit(url, competitor_url, keyword, api_key)` | Head-to-head audit with gap score (Pro+) |
51
53
  | `history(url, api_key, limit=100, since=None)` | Full audit timeseries + summary for a URL (Starter+) |
@@ -84,6 +86,31 @@ print(f"Score: {result['score']}/100 ({result['grade']})")
84
86
  Errors are `requests.HTTPError` (from `raise_for_status()`); the API's message is in
85
87
  `err.response.json()["detail"]`.
86
88
 
89
+ ## Get an API key from code
90
+
91
+ Signup takes two calls. `signup()` emails a 6-digit code (valid for 10 minutes) and
92
+ `verify()` trades that code for the key. You can also sign up at
93
+ [seoscoreapi.com](https://seoscoreapi.com/#signup).
94
+
95
+ ```python
96
+ import requests
97
+ from seoscoreapi import signup, verify
98
+
99
+ email = "you@example.com"
100
+ print(signup(email)["message"]) # "Check your email for a 6-digit verification code."
101
+
102
+ try:
103
+ key = verify(email, input("Code from the email: "))
104
+ except requests.HTTPError as e:
105
+ raise SystemExit(e.response.json()["detail"]) # wrong or expired code
106
+
107
+ print(key) # save it: the key is shown only once
108
+ ```
109
+
110
+ `signup()` raises `requests.HTTPError` when the address is refused (400) or already
111
+ has an active key (409). `verify()` raises it (400) on a wrong or expired code; five
112
+ wrong codes void the signup, so call `signup()` again.
113
+
87
114
  ## Historical tracking
88
115
 
89
116
  Every audit on a paid plan returns a `history` block on the `/audit` response:
@@ -111,6 +138,27 @@ add_monitor(
111
138
 
112
139
  Slack incoming-webhook URLs are auto-formatted as Block Kit messages; any other https endpoint receives the raw event JSON.
113
140
 
141
+ ## Core Web Vitals (opt-in)
142
+
143
+ Audits are fast because they do not wait on Google PageSpeed Insights. Ask for measured
144
+ Core Web Vitals when you need them:
145
+
146
+ ```python
147
+ result = audit("https://example.com", api_key=key, cwv=True)
148
+ vitals = result["core_web_vitals"]
149
+ if vitals["status"] == "ok":
150
+ print(vitals["lcp_ms"], vitals["inp_ms"], vitals["cls"], vitals["source"]) # source: "field" or "lab"
151
+ elif vitals.get("pending"):
152
+ print("Still measuring; audit this URL again in a minute")
153
+ else:
154
+ print("No Core Web Vitals:", vitals.get("reason"))
155
+ ```
156
+
157
+ The first measurement of a URL adds about 10 seconds (the API waits up to 25); it is
158
+ cached for 24 hours, so repeats are instant. It works on every plan and never changes the
159
+ score. Without `cwv=True` the block reads `{"status": "not_requested", ...}` with the
160
+ metrics set to `None`.
161
+
114
162
  ## Accessibility audit and report files
115
163
 
116
164
  ```python
@@ -11,10 +11,11 @@ pip install seoscoreapi
11
11
  ## Quick Start
12
12
 
13
13
  ```python
14
- from seoscoreapi import audit, signup
14
+ import os
15
+ from seoscoreapi import audit
15
16
 
16
- # Get a free API key (2 audits/day, no credit card)
17
- key = signup("you@example.com")
17
+ # Get a free API key (2 audits/day, no credit card) at https://seoscoreapi.com/#signup
18
+ key = os.environ["SEO_SCORE_API_KEY"]
18
19
 
19
20
  # Run an audit
20
21
  result = audit("https://example.com", api_key=key)
@@ -25,9 +26,10 @@ print(f"Score: {result['score']}/100 ({result['grade']})")
25
26
 
26
27
  | Function | Description |
27
28
  |---|---|
28
- | `signup(email)` | Get a free API key |
29
- | `audit(url, api_key)` | Run SEO audit on a URL |
30
- | `batch_audit(urls, api_key)` | Audit up to 10 URLs in one call (paid) |
29
+ | `signup(email)` | Start signup: the API emails a 6-digit code. Returns the API's response (`{"status": "verification_required", ...}`), not a key |
30
+ | `verify(email, code)` | Finish signup with the emailed code. Returns the API key (shown only once) |
31
+ | `audit(url, api_key, cwv=False)` | Run SEO audit on a URL. `cwv=True` adds measured Core Web Vitals (slower, see below) |
32
+ | `batch_audit(urls, api_key, cwv=False)` | Audit up to 10 URLs in one call (paid). `cwv=True` adds Core Web Vitals to each |
31
33
  | `compare(urls, api_key)` | Compare 2–5 URLs with a structured diff (Basic+) |
32
34
  | `competitive_audit(url, competitor_url, keyword, api_key)` | Head-to-head audit with gap score (Pro+) |
33
35
  | `history(url, api_key, limit=100, since=None)` | Full audit timeseries + summary for a URL (Starter+) |
@@ -66,6 +68,31 @@ print(f"Score: {result['score']}/100 ({result['grade']})")
66
68
  Errors are `requests.HTTPError` (from `raise_for_status()`); the API's message is in
67
69
  `err.response.json()["detail"]`.
68
70
 
71
+ ## Get an API key from code
72
+
73
+ Signup takes two calls. `signup()` emails a 6-digit code (valid for 10 minutes) and
74
+ `verify()` trades that code for the key. You can also sign up at
75
+ [seoscoreapi.com](https://seoscoreapi.com/#signup).
76
+
77
+ ```python
78
+ import requests
79
+ from seoscoreapi import signup, verify
80
+
81
+ email = "you@example.com"
82
+ print(signup(email)["message"]) # "Check your email for a 6-digit verification code."
83
+
84
+ try:
85
+ key = verify(email, input("Code from the email: "))
86
+ except requests.HTTPError as e:
87
+ raise SystemExit(e.response.json()["detail"]) # wrong or expired code
88
+
89
+ print(key) # save it: the key is shown only once
90
+ ```
91
+
92
+ `signup()` raises `requests.HTTPError` when the address is refused (400) or already
93
+ has an active key (409). `verify()` raises it (400) on a wrong or expired code; five
94
+ wrong codes void the signup, so call `signup()` again.
95
+
69
96
  ## Historical tracking
70
97
 
71
98
  Every audit on a paid plan returns a `history` block on the `/audit` response:
@@ -93,6 +120,27 @@ add_monitor(
93
120
 
94
121
  Slack incoming-webhook URLs are auto-formatted as Block Kit messages; any other https endpoint receives the raw event JSON.
95
122
 
123
+ ## Core Web Vitals (opt-in)
124
+
125
+ Audits are fast because they do not wait on Google PageSpeed Insights. Ask for measured
126
+ Core Web Vitals when you need them:
127
+
128
+ ```python
129
+ result = audit("https://example.com", api_key=key, cwv=True)
130
+ vitals = result["core_web_vitals"]
131
+ if vitals["status"] == "ok":
132
+ print(vitals["lcp_ms"], vitals["inp_ms"], vitals["cls"], vitals["source"]) # source: "field" or "lab"
133
+ elif vitals.get("pending"):
134
+ print("Still measuring; audit this URL again in a minute")
135
+ else:
136
+ print("No Core Web Vitals:", vitals.get("reason"))
137
+ ```
138
+
139
+ The first measurement of a URL adds about 10 seconds (the API waits up to 25); it is
140
+ cached for 24 hours, so repeats are instant. It works on every plan and never changes the
141
+ score. Without `cwv=True` the block reads `{"status": "not_requested", ...}` with the
142
+ metrics set to `None`.
143
+
96
144
  ## Accessibility audit and report files
97
145
 
98
146
  ```python
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "seoscoreapi"
7
- version = "1.6.0"
7
+ version = "1.7.0"
8
8
  description = "Python client for SEO Score API — audit any URL for SEO issues with one function call"
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
@@ -5,10 +5,11 @@ SEO Score API - Python Client
5
5
  Audit any URL for SEO issues with one function call.
6
6
 
7
7
  Usage:
8
- from seoscoreapi import audit, signup
8
+ from seoscoreapi import audit, signup, verify
9
9
 
10
- # Get a free API key
11
- key = signup("you@example.com")
10
+ # Get a free API key: signup() emails a 6-digit code, verify() returns the key
11
+ signup("you@example.com")
12
+ key = verify("you@example.com", input("Code from the email: "))
12
13
 
13
14
  # Run an audit
14
15
  result = audit("https://example.com", api_key=key)
@@ -34,29 +35,71 @@ DEEP_AUDIT_URL = os.environ.get("SEOSCORE_DEEP_AUDIT_URL") or BASE_URL
34
35
  # Legacy dedicated engine host. Still serves the same endpoints (its quota
35
36
  # endpoint is plain ``/usage``); pass it as ``base_url`` if you need it.
36
37
  ENGINE_URL = "https://engine.seoscoreapi.com"
37
- __version__ = "1.6.0"
38
+ __version__ = "1.7.0"
38
39
 
39
40
  _HEADERS = {"User-Agent": f"seoscoreapi-python/{__version__}"}
40
41
  _TIMEOUT = 30 # seconds — default per-request timeout for engine calls
41
42
 
42
43
 
43
- def signup(email: str) -> str:
44
- """Sign up for a free API key. Returns the raw key (save it — shown only once)."""
44
+ def signup(email: str) -> dict:
45
+ """Start signup for a free API key. The API emails a 6-digit code to `email`.
46
+
47
+ Returns the API's response, normally
48
+ ``{"status": "verification_required", "message": "..."}``. No key is issued
49
+ yet: pass the code to :func:`verify` to get it. The code expires after 10
50
+ minutes. Raises ``requests.HTTPError`` on a rejected address (400) or an
51
+ email that already has an active key (409).
52
+ """
45
53
  r = requests.post(f"{BASE_URL}/signup", json={"email": email}, headers=_HEADERS)
46
54
  r.raise_for_status()
55
+ return r.json()
56
+
57
+
58
+ def verify(email: str, code: str) -> str:
59
+ """Finish signup: exchange the emailed 6-digit code for the API key.
60
+
61
+ Returns the raw key (save it, it is shown only once). Raises
62
+ ``requests.HTTPError`` (400) on a wrong or expired code, or when there is no
63
+ pending signup for `email`; the reason is in ``e.response.json()["detail"]``.
64
+ Five wrong codes void the signup, so call :func:`signup` again after that.
65
+ """
66
+ r = requests.post(
67
+ f"{BASE_URL}/verify",
68
+ json={"email": email, "code": str(code).strip()},
69
+ headers=_HEADERS,
70
+ )
71
+ r.raise_for_status()
47
72
  return r.json()["api_key"]
48
73
 
49
74
 
50
- def audit(url: str, api_key: str) -> dict:
51
- """Run an SEO audit on a URL. Returns score, grade, checks, and priorities."""
52
- r = requests.get(f"{BASE_URL}/audit", params={"url": url}, headers={"X-API-Key": api_key, **_HEADERS})
75
+ def audit(url: str, api_key: str, cwv: bool = False) -> dict:
76
+ """Run an SEO audit on a URL. Returns score, grade, checks, and priorities.
77
+
78
+ ``cwv=True`` adds measured Core Web Vitals (LCP, INP, CLS, FCP, TTFB from Google
79
+ PageSpeed Insights) under ``core_web_vitals``. It is off by default because the
80
+ first measurement of a URL adds about 10 seconds (it is then cached for 24
81
+ hours). Without it ``core_web_vitals["status"]`` is ``"not_requested"``; with
82
+ it, ``"ok"``, or ``"unavailable"`` with a ``reason`` (and ``pending: True`` when
83
+ the measurement is still running: audit the URL again to pick it up).
84
+ """
85
+ params = {"url": url}
86
+ if cwv:
87
+ params["cwv"] = "true"
88
+ r = requests.get(f"{BASE_URL}/audit", params=params, headers={"X-API-Key": api_key, **_HEADERS})
53
89
  r.raise_for_status()
54
90
  return r.json()
55
91
 
56
92
 
57
- def batch_audit(urls: list[str], api_key: str) -> dict:
58
- """Audit multiple URLs (paid plans only). Returns list of results."""
59
- r = requests.post(f"{BASE_URL}/audit/batch", json={"urls": urls}, headers={"X-API-Key": api_key, **_HEADERS})
93
+ def batch_audit(urls: list[str], api_key: str, cwv: bool = False) -> dict:
94
+ """Audit multiple URLs (paid plans only). Returns list of results.
95
+
96
+ ``cwv=True`` adds measured Core Web Vitals to every result (see ``audit``). The
97
+ batch then takes longer: allow up to three minutes.
98
+ """
99
+ body: dict = {"urls": urls}
100
+ if cwv:
101
+ body["cwv"] = True
102
+ r = requests.post(f"{BASE_URL}/audit/batch", json=body, headers={"X-API-Key": api_key, **_HEADERS})
60
103
  r.raise_for_status()
61
104
  return r.json()
62
105
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: seoscoreapi
3
- Version: 1.6.0
3
+ Version: 1.7.0
4
4
  Summary: Python client for SEO Score API — audit any URL for SEO issues with one function call
5
5
  Author-email: SEO Score API <info@seoscoreapi.com>
6
6
  License: MIT
@@ -29,10 +29,11 @@ pip install seoscoreapi
29
29
  ## Quick Start
30
30
 
31
31
  ```python
32
- from seoscoreapi import audit, signup
32
+ import os
33
+ from seoscoreapi import audit
33
34
 
34
- # Get a free API key (2 audits/day, no credit card)
35
- key = signup("you@example.com")
35
+ # Get a free API key (2 audits/day, no credit card) at https://seoscoreapi.com/#signup
36
+ key = os.environ["SEO_SCORE_API_KEY"]
36
37
 
37
38
  # Run an audit
38
39
  result = audit("https://example.com", api_key=key)
@@ -43,9 +44,10 @@ print(f"Score: {result['score']}/100 ({result['grade']})")
43
44
 
44
45
  | Function | Description |
45
46
  |---|---|
46
- | `signup(email)` | Get a free API key |
47
- | `audit(url, api_key)` | Run SEO audit on a URL |
48
- | `batch_audit(urls, api_key)` | Audit up to 10 URLs in one call (paid) |
47
+ | `signup(email)` | Start signup: the API emails a 6-digit code. Returns the API's response (`{"status": "verification_required", ...}`), not a key |
48
+ | `verify(email, code)` | Finish signup with the emailed code. Returns the API key (shown only once) |
49
+ | `audit(url, api_key, cwv=False)` | Run SEO audit on a URL. `cwv=True` adds measured Core Web Vitals (slower, see below) |
50
+ | `batch_audit(urls, api_key, cwv=False)` | Audit up to 10 URLs in one call (paid). `cwv=True` adds Core Web Vitals to each |
49
51
  | `compare(urls, api_key)` | Compare 2–5 URLs with a structured diff (Basic+) |
50
52
  | `competitive_audit(url, competitor_url, keyword, api_key)` | Head-to-head audit with gap score (Pro+) |
51
53
  | `history(url, api_key, limit=100, since=None)` | Full audit timeseries + summary for a URL (Starter+) |
@@ -84,6 +86,31 @@ print(f"Score: {result['score']}/100 ({result['grade']})")
84
86
  Errors are `requests.HTTPError` (from `raise_for_status()`); the API's message is in
85
87
  `err.response.json()["detail"]`.
86
88
 
89
+ ## Get an API key from code
90
+
91
+ Signup takes two calls. `signup()` emails a 6-digit code (valid for 10 minutes) and
92
+ `verify()` trades that code for the key. You can also sign up at
93
+ [seoscoreapi.com](https://seoscoreapi.com/#signup).
94
+
95
+ ```python
96
+ import requests
97
+ from seoscoreapi import signup, verify
98
+
99
+ email = "you@example.com"
100
+ print(signup(email)["message"]) # "Check your email for a 6-digit verification code."
101
+
102
+ try:
103
+ key = verify(email, input("Code from the email: "))
104
+ except requests.HTTPError as e:
105
+ raise SystemExit(e.response.json()["detail"]) # wrong or expired code
106
+
107
+ print(key) # save it: the key is shown only once
108
+ ```
109
+
110
+ `signup()` raises `requests.HTTPError` when the address is refused (400) or already
111
+ has an active key (409). `verify()` raises it (400) on a wrong or expired code; five
112
+ wrong codes void the signup, so call `signup()` again.
113
+
87
114
  ## Historical tracking
88
115
 
89
116
  Every audit on a paid plan returns a `history` block on the `/audit` response:
@@ -111,6 +138,27 @@ add_monitor(
111
138
 
112
139
  Slack incoming-webhook URLs are auto-formatted as Block Kit messages; any other https endpoint receives the raw event JSON.
113
140
 
141
+ ## Core Web Vitals (opt-in)
142
+
143
+ Audits are fast because they do not wait on Google PageSpeed Insights. Ask for measured
144
+ Core Web Vitals when you need them:
145
+
146
+ ```python
147
+ result = audit("https://example.com", api_key=key, cwv=True)
148
+ vitals = result["core_web_vitals"]
149
+ if vitals["status"] == "ok":
150
+ print(vitals["lcp_ms"], vitals["inp_ms"], vitals["cls"], vitals["source"]) # source: "field" or "lab"
151
+ elif vitals.get("pending"):
152
+ print("Still measuring; audit this URL again in a minute")
153
+ else:
154
+ print("No Core Web Vitals:", vitals.get("reason"))
155
+ ```
156
+
157
+ The first measurement of a URL adds about 10 seconds (the API waits up to 25); it is
158
+ cached for 24 hours, so repeats are instant. It works on every plan and never changes the
159
+ score. Without `cwv=True` the block reads `{"status": "not_requested", ...}` with the
160
+ metrics set to `None`.
161
+
114
162
  ## Accessibility audit and report files
115
163
 
116
164
  ```python
@@ -32,7 +32,7 @@ def no_sleep(monkeypatch):
32
32
 
33
33
  def test_defaults_to_main_host():
34
34
  assert seo.DEEP_AUDIT_URL == "https://seoscoreapi.com"
35
- assert seo.__version__ == "1.6.0"
35
+ assert seo.__version__ == "1.7.0"
36
36
 
37
37
 
38
38
  def test_site_audit_posts_to_main_host():
@@ -202,3 +202,79 @@ def test_crawl_starts_then_polls():
202
202
  assert seo.crawl("https://e.com", "k", max_pages=5)["result"] == {"ok": 1}
203
203
  assert post.call_args[1]["json"] == {"url": "https://e.com", "max_pages": 5}
204
204
  assert get.call_args[0][0] == B + "/crawl/abc"
205
+
206
+
207
+ # --- signup is two steps: POST /signup emails a code, POST /verify returns the key
208
+
209
+ def test_signup_returns_the_verification_response_not_a_key():
210
+ payload = {"status": "verification_required", "message": "Check your email for a 6-digit verification code."}
211
+ with patched("post", payload) as m:
212
+ out = seo.signup("you@example.com")
213
+ assert out == payload
214
+ assert m.call_args.args[0] == f"{B}/signup"
215
+ assert m.call_args.kwargs["json"] == {"email": "you@example.com"}
216
+ assert "X-API-Key" not in m.call_args.kwargs["headers"]
217
+
218
+
219
+ def test_signup_passes_through_a_response_that_carries_a_key():
220
+ with patched("post", {"api_key": "ssa_x", "tier": "free"}):
221
+ assert seo.signup("you@example.com")["api_key"] == "ssa_x"
222
+
223
+
224
+ def test_signup_raises_when_the_email_already_has_a_key():
225
+ with patched("post", {"detail": "Email already has an active key (prefix: ssa_abc...)"}, status=409):
226
+ with pytest.raises(seo.requests.HTTPError):
227
+ seo.signup("you@example.com")
228
+
229
+
230
+ def test_verify_returns_the_api_key():
231
+ payload = {"api_key": "ssa_live_key", "tier": "free", "limits": {}, "message": "Save your API key"}
232
+ with patched("post", payload) as m:
233
+ assert seo.verify("you@example.com", " 012345 ") == "ssa_live_key"
234
+ assert m.call_args.args[0] == f"{B}/verify"
235
+ assert m.call_args.kwargs["json"] == {"email": "you@example.com", "code": "012345"}
236
+
237
+
238
+ def test_verify_accepts_an_integer_code():
239
+ with patched("post", {"api_key": "k"}) as m:
240
+ seo.verify("you@example.com", 123456)
241
+ assert m.call_args.kwargs["json"]["code"] == "123456"
242
+
243
+
244
+ def test_verify_raises_on_a_bad_code():
245
+ with patched("post", {"detail": "Invalid verification code. 4 attempts remaining."}, status=400):
246
+ with pytest.raises(seo.requests.HTTPError):
247
+ seo.verify("you@example.com", "000000")
248
+
249
+
250
+ # --- Core Web Vitals opt-in (1.7.0) -------------------------------------------------
251
+
252
+ def test_audit_does_not_ask_for_core_web_vitals_by_default():
253
+ with patched("get", {"score": 90}) as get:
254
+ assert seo.audit("https://e.com", "k") == {"score": 90}
255
+ args, kwargs = get.call_args
256
+ assert args[0] == B + "/audit"
257
+ assert kwargs["params"] == {"url": "https://e.com"}
258
+ assert kwargs["headers"]["X-API-Key"] == "k"
259
+
260
+
261
+ def test_audit_cwv_true_sends_the_parameter():
262
+ payload = {"score": 90, "core_web_vitals": {"status": "ok", "lcp_ms": 1800}}
263
+ with patched("get", payload) as get:
264
+ assert seo.audit("https://e.com", "k", cwv=True)["core_web_vitals"]["lcp_ms"] == 1800
265
+ assert get.call_args.kwargs["params"] == {"url": "https://e.com", "cwv": "true"}
266
+
267
+
268
+ def test_audit_cwv_false_is_the_default_request():
269
+ with patched("get", {}) as get:
270
+ seo.audit("https://e.com", "k", cwv=False)
271
+ assert get.call_args.kwargs["params"] == {"url": "https://e.com"}
272
+
273
+
274
+ def test_batch_audit_cwv():
275
+ with patched("post", {"count": 1, "results": []}) as post:
276
+ seo.batch_audit(["https://a.com"], "k")
277
+ assert post.call_args.kwargs["json"] == {"urls": ["https://a.com"]}
278
+ seo.batch_audit(["https://a.com"], "k", cwv=True)
279
+ assert post.call_args.kwargs["json"] == {"urls": ["https://a.com"], "cwv": True}
280
+ assert post.call_args.args[0] == B + "/audit/batch"
File without changes