firecrawl 4.2.0__tar.gz → 4.3.1__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.

Potentially problematic release.


This version of firecrawl might be problematic. Click here for more details.

Files changed (87) hide show
  1. {firecrawl-4.2.0 → firecrawl-4.3.1}/PKG-INFO +1 -1
  2. {firecrawl-4.2.0 → firecrawl-4.3.1}/firecrawl/__init__.py +1 -1
  3. {firecrawl-4.2.0 → firecrawl-4.3.1}/firecrawl/__tests__/e2e/v2/aio/test_aio_usage.py +7 -0
  4. {firecrawl-4.2.0 → firecrawl-4.3.1}/firecrawl/client.py +20 -5
  5. {firecrawl-4.2.0 → firecrawl-4.3.1}/firecrawl/types.py +6 -0
  6. {firecrawl-4.2.0 → firecrawl-4.3.1}/firecrawl/v1/client.py +142 -0
  7. {firecrawl-4.2.0 → firecrawl-4.3.1}/firecrawl/v2/client.py +8 -0
  8. {firecrawl-4.2.0 → firecrawl-4.3.1}/firecrawl/v2/client_async.py +12 -0
  9. firecrawl-4.3.1/firecrawl/v2/methods/aio/usage.py +89 -0
  10. {firecrawl-4.2.0 → firecrawl-4.3.1}/firecrawl/v2/methods/usage.py +31 -3
  11. {firecrawl-4.2.0 → firecrawl-4.3.1}/firecrawl/v2/types.py +26 -0
  12. {firecrawl-4.2.0 → firecrawl-4.3.1}/firecrawl.egg-info/PKG-INFO +1 -1
  13. firecrawl-4.2.0/firecrawl/v2/methods/aio/usage.py +0 -42
  14. {firecrawl-4.2.0 → firecrawl-4.3.1}/LICENSE +0 -0
  15. {firecrawl-4.2.0 → firecrawl-4.3.1}/README.md +0 -0
  16. {firecrawl-4.2.0 → firecrawl-4.3.1}/firecrawl/__tests__/e2e/v2/aio/test_aio_batch_scrape.py +0 -0
  17. {firecrawl-4.2.0 → firecrawl-4.3.1}/firecrawl/__tests__/e2e/v2/aio/test_aio_crawl.py +0 -0
  18. {firecrawl-4.2.0 → firecrawl-4.3.1}/firecrawl/__tests__/e2e/v2/aio/test_aio_extract.py +0 -0
  19. {firecrawl-4.2.0 → firecrawl-4.3.1}/firecrawl/__tests__/e2e/v2/aio/test_aio_map.py +0 -0
  20. {firecrawl-4.2.0 → firecrawl-4.3.1}/firecrawl/__tests__/e2e/v2/aio/test_aio_scrape.py +0 -0
  21. {firecrawl-4.2.0 → firecrawl-4.3.1}/firecrawl/__tests__/e2e/v2/aio/test_aio_search.py +0 -0
  22. {firecrawl-4.2.0 → firecrawl-4.3.1}/firecrawl/__tests__/e2e/v2/aio/test_aio_watcher.py +0 -0
  23. {firecrawl-4.2.0 → firecrawl-4.3.1}/firecrawl/__tests__/e2e/v2/conftest.py +0 -0
  24. {firecrawl-4.2.0 → firecrawl-4.3.1}/firecrawl/__tests__/e2e/v2/test_async.py +0 -0
  25. {firecrawl-4.2.0 → firecrawl-4.3.1}/firecrawl/__tests__/e2e/v2/test_batch_scrape.py +0 -0
  26. {firecrawl-4.2.0 → firecrawl-4.3.1}/firecrawl/__tests__/e2e/v2/test_crawl.py +0 -0
  27. {firecrawl-4.2.0 → firecrawl-4.3.1}/firecrawl/__tests__/e2e/v2/test_extract.py +0 -0
  28. {firecrawl-4.2.0 → firecrawl-4.3.1}/firecrawl/__tests__/e2e/v2/test_map.py +0 -0
  29. {firecrawl-4.2.0 → firecrawl-4.3.1}/firecrawl/__tests__/e2e/v2/test_scrape.py +0 -0
  30. {firecrawl-4.2.0 → firecrawl-4.3.1}/firecrawl/__tests__/e2e/v2/test_search.py +0 -0
  31. {firecrawl-4.2.0 → firecrawl-4.3.1}/firecrawl/__tests__/e2e/v2/test_usage.py +0 -0
  32. {firecrawl-4.2.0 → firecrawl-4.3.1}/firecrawl/__tests__/e2e/v2/test_watcher.py +0 -0
  33. {firecrawl-4.2.0 → firecrawl-4.3.1}/firecrawl/__tests__/unit/v2/methods/aio/test_aio_crawl_params.py +0 -0
  34. {firecrawl-4.2.0 → firecrawl-4.3.1}/firecrawl/__tests__/unit/v2/methods/aio/test_aio_crawl_request_preparation.py +0 -0
  35. {firecrawl-4.2.0 → firecrawl-4.3.1}/firecrawl/__tests__/unit/v2/methods/aio/test_aio_crawl_validation.py +0 -0
  36. {firecrawl-4.2.0 → firecrawl-4.3.1}/firecrawl/__tests__/unit/v2/methods/aio/test_aio_map_request_preparation.py +0 -0
  37. {firecrawl-4.2.0 → firecrawl-4.3.1}/firecrawl/__tests__/unit/v2/methods/aio/test_aio_scrape_request_preparation.py +0 -0
  38. {firecrawl-4.2.0 → firecrawl-4.3.1}/firecrawl/__tests__/unit/v2/methods/aio/test_aio_search_request_preparation.py +0 -0
  39. {firecrawl-4.2.0 → firecrawl-4.3.1}/firecrawl/__tests__/unit/v2/methods/aio/test_batch_request_preparation_async.py +0 -0
  40. {firecrawl-4.2.0 → firecrawl-4.3.1}/firecrawl/__tests__/unit/v2/methods/aio/test_ensure_async.py +0 -0
  41. {firecrawl-4.2.0 → firecrawl-4.3.1}/firecrawl/__tests__/unit/v2/methods/test_batch_request_preparation.py +0 -0
  42. {firecrawl-4.2.0 → firecrawl-4.3.1}/firecrawl/__tests__/unit/v2/methods/test_crawl_params.py +0 -0
  43. {firecrawl-4.2.0 → firecrawl-4.3.1}/firecrawl/__tests__/unit/v2/methods/test_crawl_request_preparation.py +0 -0
  44. {firecrawl-4.2.0 → firecrawl-4.3.1}/firecrawl/__tests__/unit/v2/methods/test_crawl_validation.py +0 -0
  45. {firecrawl-4.2.0 → firecrawl-4.3.1}/firecrawl/__tests__/unit/v2/methods/test_map_request_preparation.py +0 -0
  46. {firecrawl-4.2.0 → firecrawl-4.3.1}/firecrawl/__tests__/unit/v2/methods/test_pagination.py +0 -0
  47. {firecrawl-4.2.0 → firecrawl-4.3.1}/firecrawl/__tests__/unit/v2/methods/test_scrape_request_preparation.py +0 -0
  48. {firecrawl-4.2.0 → firecrawl-4.3.1}/firecrawl/__tests__/unit/v2/methods/test_search_request_preparation.py +0 -0
  49. {firecrawl-4.2.0 → firecrawl-4.3.1}/firecrawl/__tests__/unit/v2/methods/test_search_validation.py +0 -0
  50. {firecrawl-4.2.0 → firecrawl-4.3.1}/firecrawl/__tests__/unit/v2/methods/test_usage_types.py +0 -0
  51. {firecrawl-4.2.0 → firecrawl-4.3.1}/firecrawl/__tests__/unit/v2/methods/test_webhook.py +0 -0
  52. {firecrawl-4.2.0 → firecrawl-4.3.1}/firecrawl/__tests__/unit/v2/utils/test_validation.py +0 -0
  53. {firecrawl-4.2.0 → firecrawl-4.3.1}/firecrawl/__tests__/unit/v2/watcher/test_ws_watcher.py +0 -0
  54. {firecrawl-4.2.0 → firecrawl-4.3.1}/firecrawl/firecrawl.backup.py +0 -0
  55. {firecrawl-4.2.0 → firecrawl-4.3.1}/firecrawl/v1/__init__.py +0 -0
  56. {firecrawl-4.2.0 → firecrawl-4.3.1}/firecrawl/v2/__init__.py +0 -0
  57. {firecrawl-4.2.0 → firecrawl-4.3.1}/firecrawl/v2/methods/aio/__init__.py +0 -0
  58. {firecrawl-4.2.0 → firecrawl-4.3.1}/firecrawl/v2/methods/aio/batch.py +0 -0
  59. {firecrawl-4.2.0 → firecrawl-4.3.1}/firecrawl/v2/methods/aio/crawl.py +0 -0
  60. {firecrawl-4.2.0 → firecrawl-4.3.1}/firecrawl/v2/methods/aio/extract.py +0 -0
  61. {firecrawl-4.2.0 → firecrawl-4.3.1}/firecrawl/v2/methods/aio/map.py +0 -0
  62. {firecrawl-4.2.0 → firecrawl-4.3.1}/firecrawl/v2/methods/aio/scrape.py +0 -0
  63. {firecrawl-4.2.0 → firecrawl-4.3.1}/firecrawl/v2/methods/aio/search.py +0 -0
  64. {firecrawl-4.2.0 → firecrawl-4.3.1}/firecrawl/v2/methods/batch.py +0 -0
  65. {firecrawl-4.2.0 → firecrawl-4.3.1}/firecrawl/v2/methods/crawl.py +0 -0
  66. {firecrawl-4.2.0 → firecrawl-4.3.1}/firecrawl/v2/methods/extract.py +0 -0
  67. {firecrawl-4.2.0 → firecrawl-4.3.1}/firecrawl/v2/methods/map.py +0 -0
  68. {firecrawl-4.2.0 → firecrawl-4.3.1}/firecrawl/v2/methods/scrape.py +0 -0
  69. {firecrawl-4.2.0 → firecrawl-4.3.1}/firecrawl/v2/methods/search.py +0 -0
  70. {firecrawl-4.2.0 → firecrawl-4.3.1}/firecrawl/v2/utils/__init__.py +0 -0
  71. {firecrawl-4.2.0 → firecrawl-4.3.1}/firecrawl/v2/utils/error_handler.py +0 -0
  72. {firecrawl-4.2.0 → firecrawl-4.3.1}/firecrawl/v2/utils/get_version.py +0 -0
  73. {firecrawl-4.2.0 → firecrawl-4.3.1}/firecrawl/v2/utils/http_client.py +0 -0
  74. {firecrawl-4.2.0 → firecrawl-4.3.1}/firecrawl/v2/utils/http_client_async.py +0 -0
  75. {firecrawl-4.2.0 → firecrawl-4.3.1}/firecrawl/v2/utils/normalize.py +0 -0
  76. {firecrawl-4.2.0 → firecrawl-4.3.1}/firecrawl/v2/utils/validation.py +0 -0
  77. {firecrawl-4.2.0 → firecrawl-4.3.1}/firecrawl/v2/watcher.py +0 -0
  78. {firecrawl-4.2.0 → firecrawl-4.3.1}/firecrawl/v2/watcher_async.py +0 -0
  79. {firecrawl-4.2.0 → firecrawl-4.3.1}/firecrawl.egg-info/SOURCES.txt +0 -0
  80. {firecrawl-4.2.0 → firecrawl-4.3.1}/firecrawl.egg-info/dependency_links.txt +0 -0
  81. {firecrawl-4.2.0 → firecrawl-4.3.1}/firecrawl.egg-info/requires.txt +0 -0
  82. {firecrawl-4.2.0 → firecrawl-4.3.1}/firecrawl.egg-info/top_level.txt +0 -0
  83. {firecrawl-4.2.0 → firecrawl-4.3.1}/pyproject.toml +0 -0
  84. {firecrawl-4.2.0 → firecrawl-4.3.1}/setup.cfg +0 -0
  85. {firecrawl-4.2.0 → firecrawl-4.3.1}/setup.py +0 -0
  86. {firecrawl-4.2.0 → firecrawl-4.3.1}/tests/test_change_tracking.py +0 -0
  87. {firecrawl-4.2.0 → firecrawl-4.3.1}/tests/test_timeout_conversion.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: firecrawl
3
- Version: 4.2.0
3
+ Version: 4.3.1
4
4
  Summary: Python SDK for Firecrawl API
5
5
  Home-page: https://github.com/firecrawl/firecrawl
6
6
  Author: Mendable.ai
@@ -17,7 +17,7 @@ from .v1 import (
17
17
  V1ChangeTrackingOptions,
18
18
  )
19
19
 
20
- __version__ = "4.2.0"
20
+ __version__ = "4.3.1"
21
21
 
22
22
  # Define the logger for the Firecrawl project
23
23
  logger: logging.Logger = logging.getLogger("firecrawl")
@@ -33,3 +33,10 @@ async def test_async_get_token_usage():
33
33
  tokens = await client.get_token_usage()
34
34
  assert hasattr(tokens, "remaining_tokens")
35
35
 
36
+
37
+ @pytest.mark.asyncio
38
+ async def test_async_get_queue_status():
39
+ client = AsyncFirecrawl(api_key=os.getenv("API_KEY"), api_url=os.getenv("API_URL"))
40
+ status = await client.get_queue_status()
41
+ assert hasattr(status, "jobs_in_queue")
42
+
@@ -56,23 +56,34 @@ class V2Proxy:
56
56
  self._client = client_instance
57
57
 
58
58
  if client_instance:
59
+ self.scrape = client_instance.scrape
59
60
  self.search = client_instance.search
60
61
  self.crawl = client_instance.crawl
62
+ self.start_crawl = client_instance.start_crawl
61
63
  self.get_crawl_status = client_instance.get_crawl_status
62
64
  self.cancel_crawl = client_instance.cancel_crawl
63
- self.start_crawl = client_instance.start_crawl
65
+ self.get_crawl_errors = client_instance.get_crawl_errors
66
+ self.get_active_crawls = client_instance.get_active_crawls
67
+ self.active_crawls = client_instance.active_crawls
64
68
  self.crawl_params_preview = client_instance.crawl_params_preview
69
+
65
70
  self.extract = client_instance.extract
71
+ self.start_extract = client_instance.start_extract
72
+ self.get_extract_status = client_instance.get_extract_status
73
+
66
74
  self.start_batch_scrape = client_instance.start_batch_scrape
67
75
  self.get_batch_scrape_status = client_instance.get_batch_scrape_status
68
76
  self.cancel_batch_scrape = client_instance.cancel_batch_scrape
69
77
  self.batch_scrape = client_instance.batch_scrape
70
78
  self.get_batch_scrape_errors = client_instance.get_batch_scrape_errors
71
- self.get_extract_status = client_instance.get_extract_status
79
+
72
80
  self.map = client_instance.map
73
81
  self.get_concurrency = client_instance.get_concurrency
74
82
  self.get_credit_usage = client_instance.get_credit_usage
75
83
  self.get_token_usage = client_instance.get_token_usage
84
+ self.get_queue_status = client_instance.get_queue_status
85
+
86
+ self.watcher = client_instance.watcher
76
87
 
77
88
  def __getattr__(self, name):
78
89
  """Forward attribute access to the underlying client."""
@@ -99,9 +110,9 @@ class AsyncV1Proxy:
99
110
 
100
111
  class AsyncV2Proxy:
101
112
  """Proxy class that forwards method calls to the appropriate version client."""
102
- _client: Optional[Any] = None
113
+ _client: Optional[AsyncFirecrawlClient] = None
103
114
 
104
- def __init__(self, client_instance: Optional[Any] = None):
115
+ def __init__(self, client_instance: Optional[AsyncFirecrawlClient] = None):
105
116
  self._client = client_instance
106
117
 
107
118
  if client_instance:
@@ -132,6 +143,8 @@ class AsyncV2Proxy:
132
143
  self.get_concurrency = client_instance.get_concurrency
133
144
  self.get_credit_usage = client_instance.get_credit_usage
134
145
  self.get_token_usage = client_instance.get_token_usage
146
+ self.get_queue_status = client_instance.get_queue_status
147
+
135
148
  self.watcher = client_instance.watcher
136
149
 
137
150
  def __getattr__(self, name):
@@ -193,7 +206,8 @@ class Firecrawl:
193
206
  self.get_concurrency = self._v2_client.get_concurrency
194
207
  self.get_credit_usage = self._v2_client.get_credit_usage
195
208
  self.get_token_usage = self._v2_client.get_token_usage
196
-
209
+ self.get_queue_status = self._v2_client.get_queue_status
210
+
197
211
  self.watcher = self._v2_client.watcher
198
212
 
199
213
  class AsyncFirecrawl:
@@ -238,6 +252,7 @@ class AsyncFirecrawl:
238
252
  self.get_concurrency = self._v2_client.get_concurrency
239
253
  self.get_credit_usage = self._v2_client.get_credit_usage
240
254
  self.get_token_usage = self._v2_client.get_token_usage
255
+ self.get_queue_status = self._v2_client.get_queue_status
241
256
 
242
257
  self.watcher = self._v2_client.watcher
243
258
 
@@ -65,6 +65,9 @@ from .v2.types import (
65
65
  ExecuteJavascriptAction,
66
66
  PDFAction,
67
67
 
68
+ # Usage types
69
+ QueueStatusResponse,
70
+
68
71
  # Location and format types
69
72
  Location,
70
73
 
@@ -142,6 +145,9 @@ __all__ = [
142
145
  'ScrapeAction',
143
146
  'ExecuteJavascriptAction',
144
147
  'PDFAction',
148
+
149
+ # Usage types
150
+ 'QueueStatusResponse',
145
151
 
146
152
  # Location and format types
147
153
  'Location',
@@ -358,6 +358,46 @@ class V1SearchResponse(pydantic.BaseModel):
358
358
  warning: Optional[str] = None
359
359
  error: Optional[str] = None
360
360
 
361
+ class V1CreditUsageData(pydantic.BaseModel):
362
+ remaining_credits: int
363
+ plan_credits: Optional[int] = None
364
+ billing_period_start: Optional[str] = None
365
+ billing_period_end: Optional[str] = None
366
+
367
+ class V1CreditUsageResponse(pydantic.BaseModel):
368
+ success: bool
369
+ data: V1CreditUsageData
370
+
371
+ class V1TokenUsageData(pydantic.BaseModel):
372
+ remaining_tokens: int
373
+ plan_tokens: Optional[int] = None
374
+ billing_period_start: Optional[str] = None
375
+ billing_period_end: Optional[str] = None
376
+
377
+ class V1TokenUsageResponse(pydantic.BaseModel):
378
+ success: bool
379
+ data: V1TokenUsageData
380
+
381
+ class V1CreditUsageHistoricalPeriod(pydantic.BaseModel):
382
+ startDate: Optional[str] = None
383
+ endDate: Optional[str] = None
384
+ apiKey: Optional[str] = None
385
+ creditsUsed: int
386
+
387
+ class V1CreditUsageHistoricalResponse(pydantic.BaseModel):
388
+ success: bool
389
+ periods: List[V1CreditUsageHistoricalPeriod]
390
+
391
+ class V1TokenUsageHistoricalPeriod(pydantic.BaseModel):
392
+ startDate: Optional[str] = None
393
+ endDate: Optional[str] = None
394
+ apiKey: Optional[str] = None
395
+ tokensUsed: int
396
+
397
+ class V1TokenUsageHistoricalResponse(pydantic.BaseModel):
398
+ success: bool
399
+ periods: List[V1TokenUsageHistoricalPeriod]
400
+
361
401
  class V1GenerateLLMsTextParams(pydantic.BaseModel):
362
402
  """
363
403
  Parameters for the LLMs.txt generation operation.
@@ -722,6 +762,90 @@ class V1FirecrawlApp:
722
762
  else:
723
763
  self._handle_error(response, 'search')
724
764
 
765
+ def get_credit_usage(self) -> V1CreditUsageResponse:
766
+ """Get current credit usage and billing period (v1)."""
767
+ _headers = self._prepare_headers()
768
+ response = self._get_request(
769
+ f"{self.api_url}/v1/team/credit-usage",
770
+ _headers
771
+ )
772
+
773
+ if response.status_code == 200:
774
+ try:
775
+ response_json = response.json()
776
+ if response_json.get('success') and 'data' in response_json:
777
+ return V1CreditUsageResponse(**response_json)
778
+ elif "error" in response_json:
779
+ raise Exception(f"Failed to get credit usage. Error: {response_json['error']}")
780
+ else:
781
+ raise Exception(f"Failed to get credit usage. Error: {response_json}")
782
+ except ValueError:
783
+ raise Exception('Failed to parse Firecrawl response as JSON.')
784
+ else:
785
+ self._handle_error(response, 'get credit usage')
786
+
787
+ def get_token_usage(self) -> V1TokenUsageResponse:
788
+ """Get current token usage and billing period (v1)."""
789
+ _headers = self._prepare_headers()
790
+ response = self._get_request(
791
+ f"{self.api_url}/v1/team/token-usage",
792
+ _headers
793
+ )
794
+
795
+ if response.status_code == 200:
796
+ try:
797
+ response_json = response.json()
798
+ if response_json.get('success') and 'data' in response_json:
799
+ return V1TokenUsageResponse(**response_json)
800
+ elif "error" in response_json:
801
+ raise Exception(f"Failed to get token usage. Error: {response_json['error']}")
802
+ else:
803
+ raise Exception(f"Failed to get token usage. Error: {response_json}")
804
+ except ValueError:
805
+ raise Exception('Failed to parse Firecrawl response as JSON.')
806
+ else:
807
+ self._handle_error(response, 'get token usage')
808
+
809
+ def get_credit_usage_historical(self, by_api_key: bool = False) -> V1CreditUsageHistoricalResponse:
810
+ """Get historical credit usage (v1)."""
811
+ _headers = self._prepare_headers()
812
+ url = f"{self.api_url}/v1/team/credit-usage/historical" + ("?byApiKey=true" if by_api_key else "")
813
+ response = self._get_request(url, _headers)
814
+
815
+ if response.status_code == 200:
816
+ try:
817
+ response_json = response.json()
818
+ if response_json.get('success') and 'periods' in response_json:
819
+ return V1CreditUsageHistoricalResponse(**response_json)
820
+ elif "error" in response_json:
821
+ raise Exception(f"Failed to get historical credit usage. Error: {response_json['error']}")
822
+ else:
823
+ raise Exception(f"Failed to get historical credit usage. Error: {response_json}")
824
+ except ValueError:
825
+ raise Exception('Failed to parse Firecrawl response as JSON.')
826
+ else:
827
+ self._handle_error(response, 'get credit usage historical')
828
+
829
+ def get_token_usage_historical(self, by_api_key: bool = False) -> V1TokenUsageHistoricalResponse:
830
+ """Get historical token usage (v1)."""
831
+ _headers = self._prepare_headers()
832
+ url = f"{self.api_url}/v1/team/token-usage/historical" + ("?byApiKey=true" if by_api_key else "")
833
+ response = self._get_request(url, _headers)
834
+
835
+ if response.status_code == 200:
836
+ try:
837
+ response_json = response.json()
838
+ if response_json.get('success') and 'periods' in response_json:
839
+ return V1TokenUsageHistoricalResponse(**response_json)
840
+ elif "error" in response_json:
841
+ raise Exception(f"Failed to get historical token usage. Error: {response_json['error']}")
842
+ else:
843
+ raise Exception(f"Failed to get historical token usage. Error: {response_json}")
844
+ except ValueError:
845
+ raise Exception('Failed to parse Firecrawl response as JSON.')
846
+ else:
847
+ self._handle_error(response, 'get token usage historical')
848
+
725
849
  def crawl_url(
726
850
  self,
727
851
  url: str,
@@ -2913,6 +3037,24 @@ class AsyncV1FirecrawlApp(V1FirecrawlApp):
2913
3037
  """
2914
3038
  return self._get_error_message(status_code, action, error_message, error_details)
2915
3039
 
3040
+ async def get_credit_usage(self) -> V1CreditUsageResponse:
3041
+ """Get current credit usage and billing period (v1, async)."""
3042
+ headers = self._prepare_headers()
3043
+ resp = await self._async_get_request(
3044
+ f"{self.api_url}/v1/team/credit-usage",
3045
+ headers
3046
+ )
3047
+ return V1CreditUsageResponse(**resp)
3048
+
3049
+ async def get_token_usage(self) -> V1TokenUsageResponse:
3050
+ """Get current token usage and billing period (v1, async)."""
3051
+ headers = self._prepare_headers()
3052
+ resp = await self._async_get_request(
3053
+ f"{self.api_url}/v1/team/token-usage",
3054
+ headers
3055
+ )
3056
+ return V1TokenUsageResponse(**resp)
3057
+
2916
3058
  async def crawl_url_and_watch(
2917
3059
  self,
2918
3060
  url: str,
@@ -727,6 +727,14 @@ class FirecrawlClient:
727
727
  """Get recent token usage metrics (v2)."""
728
728
  return usage_methods.get_token_usage(self.http_client)
729
729
 
730
+ def get_credit_usage_historical(self, by_api_key: bool = False):
731
+ """Get historical credit usage (v2)."""
732
+ return usage_methods.get_credit_usage_historical(self.http_client, by_api_key)
733
+
734
+ def get_token_usage_historical(self, by_api_key: bool = False):
735
+ """Get historical token usage (v2)."""
736
+ return usage_methods.get_token_usage_historical(self.http_client, by_api_key)
737
+
730
738
  def get_queue_status(self):
731
739
  """Get metrics about the team's scrape queue."""
732
740
  return usage_methods.get_queue_status(self.http_client)
@@ -253,6 +253,18 @@ class AsyncFirecrawlClient:
253
253
  async def get_token_usage(self):
254
254
  from .methods.aio import usage as async_usage # type: ignore[attr-defined]
255
255
  return await async_usage.get_token_usage(self.async_http_client)
256
+
257
+ async def get_credit_usage_historical(self, by_api_key: bool = False):
258
+ from .methods.aio import usage as async_usage # type: ignore[attr-defined]
259
+ return await async_usage.get_credit_usage_historical(self.async_http_client, by_api_key)
260
+
261
+ async def get_token_usage_historical(self, by_api_key: bool = False):
262
+ from .methods.aio import usage as async_usage # type: ignore[attr-defined]
263
+ return await async_usage.get_token_usage_historical(self.async_http_client, by_api_key)
264
+
265
+ async def get_queue_status(self):
266
+ from .methods.aio import usage as async_usage # type: ignore[attr-defined]
267
+ return await async_usage.get_queue_status(self.async_http_client)
256
268
 
257
269
  # Watcher (sync object usable from async contexts)
258
270
  def watcher(
@@ -0,0 +1,89 @@
1
+ from ...utils.http_client_async import AsyncHttpClient
2
+ from ...utils.error_handler import handle_response_error
3
+ from ...types import ConcurrencyCheck, CreditUsage, TokenUsage, CreditUsageHistoricalResponse, TokenUsageHistoricalResponse, QueueStatusResponse
4
+
5
+
6
+ async def get_concurrency(client: AsyncHttpClient) -> ConcurrencyCheck:
7
+ resp = await client.get("/v2/concurrency-check")
8
+ if resp.status_code >= 400:
9
+ handle_response_error(resp, "get concurrency")
10
+ body = resp.json()
11
+ if not body.get("success"):
12
+ raise Exception(body.get("error", "Unknown error"))
13
+ data = body.get("data", body)
14
+ return ConcurrencyCheck(
15
+ concurrency=data.get("concurrency"),
16
+ max_concurrency=data.get("maxConcurrency", data.get("max_concurrency")),
17
+ )
18
+
19
+
20
+ async def get_credit_usage(client: AsyncHttpClient) -> CreditUsage:
21
+ resp = await client.get("/v2/team/credit-usage")
22
+ if resp.status_code >= 400:
23
+ handle_response_error(resp, "get credit usage")
24
+ body = resp.json()
25
+ if not body.get("success"):
26
+ raise Exception(body.get("error", "Unknown error"))
27
+ data = body.get("data", body)
28
+ return CreditUsage(
29
+ remaining_credits=data.get("remainingCredits", data.get("remaining_credits", 0)),
30
+ plan_credits=data.get("planCredits", data.get("plan_credits")),
31
+ billing_period_start=data.get("billingPeriodStart", data.get("billing_period_start")),
32
+ billing_period_end=data.get("billingPeriodEnd", data.get("billing_period_end")),
33
+ )
34
+
35
+
36
+ async def get_token_usage(client: AsyncHttpClient) -> TokenUsage:
37
+ resp = await client.get("/v2/team/token-usage")
38
+ if resp.status_code >= 400:
39
+ handle_response_error(resp, "get token usage")
40
+ body = resp.json()
41
+ if not body.get("success"):
42
+ raise Exception(body.get("error", "Unknown error"))
43
+ data = body.get("data", body)
44
+ return TokenUsage(
45
+ remaining_tokens=data.get("remainingTokens", data.get("remaining_tokens", 0)),
46
+ plan_tokens=data.get("planTokens", data.get("plan_tokens")),
47
+ billing_period_start=data.get("billingPeriodStart", data.get("billing_period_start")),
48
+ billing_period_end=data.get("billingPeriodEnd", data.get("billing_period_end")),
49
+ )
50
+
51
+
52
+ async def get_queue_status(client: AsyncHttpClient) -> QueueStatusResponse:
53
+ resp = await client.get("/v2/team/queue-status")
54
+ if resp.status_code >= 400:
55
+ handle_response_error(resp, "get queue status")
56
+ body = resp.json()
57
+ if not body.get("success"):
58
+ raise Exception(body.get("error", "Unknown error"))
59
+ data = body.get("data", body)
60
+ return QueueStatusResponse(
61
+ jobs_in_queue=data.get("jobsInQueue", 0),
62
+ active_jobs_in_queue=data.get("activeJobsInQueue", 0),
63
+ waiting_jobs_in_queue=data.get("waitingJobsInQueue", 0),
64
+ max_concurrency=data.get("maxConcurrency", 0),
65
+ most_recent_success=data.get("mostRecentSuccess", None),
66
+ )
67
+
68
+
69
+ async def get_credit_usage_historical(client: AsyncHttpClient, by_api_key: bool = False) -> CreditUsageHistoricalResponse:
70
+ query = "?byApiKey=true" if by_api_key else ""
71
+ resp = await client.get(f"/v2/team/credit-usage/historical{query}")
72
+ if resp.status_code >= 400:
73
+ handle_response_error(resp, "get credit usage historical")
74
+ body = resp.json()
75
+ if not body.get("success"):
76
+ raise Exception(body.get("error", "Unknown error"))
77
+ return CreditUsageHistoricalResponse(**body)
78
+
79
+
80
+ async def get_token_usage_historical(client: AsyncHttpClient, by_api_key: bool = False) -> TokenUsageHistoricalResponse:
81
+ query = "?byApiKey=true" if by_api_key else ""
82
+ resp = await client.get(f"/v2/team/token-usage/historical{query}")
83
+ if resp.status_code >= 400:
84
+ handle_response_error(resp, "get token usage historical")
85
+ body = resp.json()
86
+ if not body.get("success"):
87
+ raise Exception(body.get("error", "Unknown error"))
88
+ return TokenUsageHistoricalResponse(**body)
89
+
@@ -1,5 +1,5 @@
1
1
  from ..utils import HttpClient, handle_response_error
2
- from ..types import ConcurrencyCheck, CreditUsage, QueueStatusResponse, TokenUsage
2
+ from ..types import ConcurrencyCheck, CreditUsage, QueueStatusResponse, TokenUsage, CreditUsageHistoricalResponse, TokenUsageHistoricalResponse
3
3
 
4
4
 
5
5
  def get_concurrency(client: HttpClient) -> ConcurrencyCheck:
@@ -24,7 +24,12 @@ def get_credit_usage(client: HttpClient) -> CreditUsage:
24
24
  if not body.get("success"):
25
25
  raise Exception(body.get("error", "Unknown error"))
26
26
  data = body.get("data", body)
27
- return CreditUsage(remaining_credits=data.get("remainingCredits", data.get("remaining_credits", 0)))
27
+ return CreditUsage(
28
+ remaining_credits=data.get("remainingCredits", data.get("remaining_credits", 0)),
29
+ plan_credits=data.get("planCredits", data.get("plan_credits")),
30
+ billing_period_start=data.get("billingPeriodStart", data.get("billing_period_start")),
31
+ billing_period_end=data.get("billingPeriodEnd", data.get("billing_period_end")),
32
+ )
28
33
 
29
34
 
30
35
  def get_token_usage(client: HttpClient) -> TokenUsage:
@@ -36,7 +41,10 @@ def get_token_usage(client: HttpClient) -> TokenUsage:
36
41
  raise Exception(body.get("error", "Unknown error"))
37
42
  data = body.get("data", body)
38
43
  return TokenUsage(
39
- remaining_tokens=data.get("remainingTokens", 0)
44
+ remaining_tokens=data.get("remainingTokens", data.get("remaining_tokens", 0)),
45
+ plan_tokens=data.get("planTokens", data.get("plan_tokens")),
46
+ billing_period_start=data.get("billingPeriodStart", data.get("billing_period_start")),
47
+ billing_period_end=data.get("billingPeriodEnd", data.get("billing_period_end")),
40
48
  )
41
49
 
42
50
  def get_queue_status(client: HttpClient) -> QueueStatusResponse:
@@ -54,3 +62,23 @@ def get_queue_status(client: HttpClient) -> QueueStatusResponse:
54
62
  max_concurrency=data.get("maxConcurrency", 0),
55
63
  most_recent_success=data.get("mostRecentSuccess", None),
56
64
  )
65
+
66
+
67
+ def get_credit_usage_historical(client: HttpClient, by_api_key: bool = False) -> CreditUsageHistoricalResponse:
68
+ resp = client.get(f"/v2/team/credit-usage/historical{'?byApiKey=true' if by_api_key else ''}")
69
+ if not resp.ok:
70
+ handle_response_error(resp, "get credit usage historical")
71
+ body = resp.json()
72
+ if not body.get("success"):
73
+ raise Exception(body.get("error", "Unknown error"))
74
+ return CreditUsageHistoricalResponse(**body)
75
+
76
+
77
+ def get_token_usage_historical(client: HttpClient, by_api_key: bool = False) -> TokenUsageHistoricalResponse:
78
+ resp = client.get(f"/v2/team/token-usage/historical{'?byApiKey=true' if by_api_key else ''}")
79
+ if not resp.ok:
80
+ handle_response_error(resp, "get token usage historical")
81
+ body = resp.json()
82
+ if not body.get("success"):
83
+ raise Exception(body.get("error", "Unknown error"))
84
+ return TokenUsageHistoricalResponse(**body)
@@ -480,10 +480,16 @@ class ConcurrencyCheck(BaseModel):
480
480
  class CreditUsage(BaseModel):
481
481
  """Remaining credits for the team/API key."""
482
482
  remaining_credits: int
483
+ plan_credits: Optional[int] = None
484
+ billing_period_start: Optional[str] = None
485
+ billing_period_end: Optional[str] = None
483
486
 
484
487
  class TokenUsage(BaseModel):
485
488
  """Recent token usage metrics (if available)."""
486
489
  remaining_tokens: int
490
+ plan_tokens: Optional[int] = None
491
+ billing_period_start: Optional[str] = None
492
+ billing_period_end: Optional[str] = None
487
493
 
488
494
  class QueueStatusResponse(BaseModel):
489
495
  """Metrics about the team's scrape queue."""
@@ -493,6 +499,26 @@ class QueueStatusResponse(BaseModel):
493
499
  max_concurrency: int
494
500
  most_recent_success: Optional[datetime] = None
495
501
 
502
+ class CreditUsageHistoricalPeriod(BaseModel):
503
+ startDate: Optional[str] = None
504
+ endDate: Optional[str] = None
505
+ apiKey: Optional[str] = None
506
+ creditsUsed: int
507
+
508
+ class CreditUsageHistoricalResponse(BaseModel):
509
+ success: bool
510
+ periods: List[CreditUsageHistoricalPeriod]
511
+
512
+ class TokenUsageHistoricalPeriod(BaseModel):
513
+ startDate: Optional[str] = None
514
+ endDate: Optional[str] = None
515
+ apiKey: Optional[str] = None
516
+ tokensUsed: int
517
+
518
+ class TokenUsageHistoricalResponse(BaseModel):
519
+ success: bool
520
+ periods: List[TokenUsageHistoricalPeriod]
521
+
496
522
  # Action types
497
523
  class WaitAction(BaseModel):
498
524
  """Wait action to perform during scraping."""
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: firecrawl
3
- Version: 4.2.0
3
+ Version: 4.3.1
4
4
  Summary: Python SDK for Firecrawl API
5
5
  Home-page: https://github.com/firecrawl/firecrawl
6
6
  Author: Mendable.ai
@@ -1,42 +0,0 @@
1
- from ...utils.http_client_async import AsyncHttpClient
2
- from ...utils.error_handler import handle_response_error
3
- from ...types import ConcurrencyCheck, CreditUsage, TokenUsage
4
-
5
-
6
- async def get_concurrency(client: AsyncHttpClient) -> ConcurrencyCheck:
7
- resp = await client.get("/v2/concurrency-check")
8
- if resp.status_code >= 400:
9
- handle_response_error(resp, "get concurrency")
10
- body = resp.json()
11
- if not body.get("success"):
12
- raise Exception(body.get("error", "Unknown error"))
13
- data = body.get("data", body)
14
- return ConcurrencyCheck(
15
- concurrency=data.get("concurrency"),
16
- max_concurrency=data.get("maxConcurrency", data.get("max_concurrency")),
17
- )
18
-
19
-
20
- async def get_credit_usage(client: AsyncHttpClient) -> CreditUsage:
21
- resp = await client.get("/v2/team/credit-usage")
22
- if resp.status_code >= 400:
23
- handle_response_error(resp, "get credit usage")
24
- body = resp.json()
25
- if not body.get("success"):
26
- raise Exception(body.get("error", "Unknown error"))
27
- data = body.get("data", body)
28
- return CreditUsage(remaining_credits=data.get("remainingCredits", data.get("remaining_credits", 0)))
29
-
30
-
31
- async def get_token_usage(client: AsyncHttpClient) -> TokenUsage:
32
- resp = await client.get("/v2/team/token-usage")
33
- if resp.status_code >= 400:
34
- handle_response_error(resp, "get token usage")
35
- body = resp.json()
36
- if not body.get("success"):
37
- raise Exception(body.get("error", "Unknown error"))
38
- data = body.get("data", body)
39
- return TokenUsage(
40
- remaining_tokens=data.get("remainingTokens", 0)
41
- )
42
-
File without changes
File without changes
File without changes
File without changes
File without changes