transcodely 0.3.6__tar.gz → 0.3.8__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. {transcodely-0.3.6 → transcodely-0.3.8}/PKG-INFO +53 -2
  2. {transcodely-0.3.6 → transcodely-0.3.8}/README.md +51 -0
  3. {transcodely-0.3.6 → transcodely-0.3.8}/pyproject.toml +1 -1
  4. {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/__init__.py +10 -0
  5. transcodely-0.3.8/src/transcodely/resources/billing.py +266 -0
  6. {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/types.py +71 -0
  7. transcodely-0.3.8/src/transcodely/v1/app_pb2.py +129 -0
  8. {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/v1/app_pb2.pyi +36 -2
  9. transcodely-0.3.8/src/transcodely/v1/billing_pb2.py +109 -0
  10. transcodely-0.3.8/src/transcodely/v1/billing_pb2.pyi +356 -0
  11. {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/v1/common_pb2.py +7 -1
  12. {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/v1/common_pb2.pyi +43 -0
  13. transcodely-0.3.8/src/transcodely/v1/job_pb2.py +236 -0
  14. transcodely-0.3.8/src/transcodely/v1/organization_pb2.py +81 -0
  15. {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/v1/organization_pb2.pyi +22 -2
  16. {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/v1/streaming_pb2.py +9 -9
  17. transcodely-0.3.8/src/transcodely/v1/subtitles_pb2.py +75 -0
  18. transcodely-0.3.8/src/transcodely/v1/video_pb2.py +210 -0
  19. {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/v1/video_pb2.pyi +6 -2
  20. {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/version.py +1 -1
  21. transcodely-0.3.6/src/transcodely/resources/billing.py +0 -90
  22. transcodely-0.3.6/src/transcodely/v1/app_pb2.py +0 -111
  23. transcodely-0.3.6/src/transcodely/v1/billing_pb2.py +0 -65
  24. transcodely-0.3.6/src/transcodely/v1/billing_pb2.pyi +0 -131
  25. transcodely-0.3.6/src/transcodely/v1/job_pb2.py +0 -232
  26. transcodely-0.3.6/src/transcodely/v1/organization_pb2.py +0 -81
  27. transcodely-0.3.6/src/transcodely/v1/subtitles_pb2.py +0 -75
  28. transcodely-0.3.6/src/transcodely/v1/video_pb2.py +0 -210
  29. {transcodely-0.3.6 → transcodely-0.3.8}/.gitignore +0 -0
  30. {transcodely-0.3.6 → transcodely-0.3.8}/LICENSE +0 -0
  31. {transcodely-0.3.6 → transcodely-0.3.8}/src/buf/__init__.py +0 -0
  32. {transcodely-0.3.6 → transcodely-0.3.8}/src/buf/validate/__init__.py +0 -0
  33. {transcodely-0.3.6 → transcodely-0.3.8}/src/buf/validate/validate_pb2.py +0 -0
  34. {transcodely-0.3.6 → transcodely-0.3.8}/src/buf/validate/validate_pb2.pyi +0 -0
  35. {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/_codec/__init__.py +0 -0
  36. {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/_codec/json_codec.py +0 -0
  37. {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/_transport/__init__.py +0 -0
  38. {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/_transport/error_mapping.py +0 -0
  39. {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/_transport/headers.py +0 -0
  40. {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/_transport/transport.py +0 -0
  41. {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/client.py +0 -0
  42. {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/errors.py +0 -0
  43. {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/pagination.py +0 -0
  44. {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/py.typed +0 -0
  45. {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/resources/__init__.py +0 -0
  46. {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/resources/_helpers.py +0 -0
  47. {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/resources/api_keys.py +0 -0
  48. {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/resources/apps.py +0 -0
  49. {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/resources/events.py +0 -0
  50. {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/resources/health.py +0 -0
  51. {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/resources/jobs.py +0 -0
  52. {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/resources/memberships.py +0 -0
  53. {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/resources/organizations.py +0 -0
  54. {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/resources/origins.py +0 -0
  55. {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/resources/presets.py +0 -0
  56. {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/resources/users.py +0 -0
  57. {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/resources/videos.py +0 -0
  58. {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/resources/webhook_endpoints.py +0 -0
  59. {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/streaming.py +0 -0
  60. {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/v1/api_key_pb2.py +0 -0
  61. {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/v1/api_key_pb2.pyi +0 -0
  62. {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/v1/codec_av1_pb2.py +0 -0
  63. {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/v1/codec_av1_pb2.pyi +0 -0
  64. {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/v1/codec_h264_pb2.py +0 -0
  65. {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/v1/codec_h264_pb2.pyi +0 -0
  66. {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/v1/codec_h265_pb2.py +0 -0
  67. {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/v1/codec_h265_pb2.pyi +0 -0
  68. {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/v1/codec_vp9_pb2.py +0 -0
  69. {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/v1/codec_vp9_pb2.pyi +0 -0
  70. {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/v1/content_aware_pb2.py +0 -0
  71. {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/v1/content_aware_pb2.pyi +0 -0
  72. {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/v1/drm_pb2.py +0 -0
  73. {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/v1/drm_pb2.pyi +0 -0
  74. {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/v1/hdr_pb2.py +0 -0
  75. {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/v1/hdr_pb2.pyi +0 -0
  76. {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/v1/health_pb2.py +0 -0
  77. {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/v1/health_pb2.pyi +0 -0
  78. {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/v1/job_pb2.pyi +0 -0
  79. {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/v1/media_pb2.py +0 -0
  80. {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/v1/media_pb2.pyi +0 -0
  81. {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/v1/membership_pb2.py +0 -0
  82. {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/v1/membership_pb2.pyi +0 -0
  83. {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/v1/origin_pb2.py +0 -0
  84. {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/v1/origin_pb2.pyi +0 -0
  85. {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/v1/preset_pb2.py +0 -0
  86. {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/v1/preset_pb2.pyi +0 -0
  87. {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/v1/streaming_pb2.pyi +0 -0
  88. {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/v1/subtitles_pb2.pyi +0 -0
  89. {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/v1/thumbnails_pb2.py +0 -0
  90. {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/v1/thumbnails_pb2.pyi +0 -0
  91. {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/v1/user_pb2.py +0 -0
  92. {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/v1/user_pb2.pyi +0 -0
  93. {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/v1/watermark_pb2.py +0 -0
  94. {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/v1/watermark_pb2.pyi +0 -0
  95. {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/v1/webhook_pb2.py +0 -0
  96. {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/v1/webhook_pb2.pyi +0 -0
  97. {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/webhooks/__init__.py +0 -0
  98. {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/webhooks/construct_event.py +0 -0
  99. {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/webhooks/signature.py +0 -0
  100. {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/webhooks/types.py +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: transcodely
3
- Version: 0.3.6
3
+ Version: 0.3.8
4
4
  Summary: Official Python SDK for the Transcodely video transcoding API
5
5
  Project-URL: Homepage, https://transcodely.com
6
6
  Project-URL: Documentation, https://transcodely.com/docs
@@ -122,6 +122,9 @@ client.origins # create / get / list / update / validate / archive
122
122
  client.apps # create / get / list / update / archive / enable_hosting
123
123
  client.api_keys # create / get / list / revoke
124
124
  client.organizations # create / get / list / update / check_slug
125
+ client.billing # list_invoices / retrieve / retrieve_upcoming / retrieve_profile / create_portal_session
126
+ # + retrieve_budget / set_budget / clear_budget
127
+ # + retrieve_outstanding_balance / settle_outstanding_balance
125
128
  client.memberships # list / get / update_role / remove
126
129
  client.users # get_me / get / list / update_me
127
130
  client.health # check
@@ -266,6 +269,54 @@ r2 = {
266
269
 
267
270
  Provide either `account_id` or `endpoint`, never both. `jurisdiction` only applies when `account_id` is set.
268
271
 
272
+ ## Billing
273
+
274
+ Billing settles a whole organization, so `client.billing` needs a dashboard session token for an
275
+ organization **owner** plus the organization it is for — an API key is scoped to one app and is
276
+ rejected outright:
277
+
278
+ ```python
279
+ client = Transcodely("tk_session_token", organization_id="org_f6g7h8i9j0")
280
+
281
+ client.billing.retrieve_upcoming() # what the current period has accrued
282
+ client.billing.list_invoices(limit=12)
283
+ ```
284
+
285
+ Three numbers live here and they are not interchangeable:
286
+
287
+ | Call | What it answers | Enforces? |
288
+ |---|---|---|
289
+ | `billing.retrieve_upcoming()` | What the **current period** has accrued | No |
290
+ | `billing.retrieve_budget()` | Spend against the org's own monthly budget | No — emails only |
291
+ | `billing.retrieve_outstanding_balance()` | What is **unsettled**, incl. earlier periods | Yes, at the hard stop |
292
+ | `apps.set_spend_limit(app_id, eur)` | One app's monthly cap | Yes, at 100% |
293
+
294
+ Budgets notify; limits block:
295
+
296
+ ```python
297
+ client.billing.set_budget(500.0) # emails at 50% / 80% / 100%, restricts nothing
298
+ client.billing.clear_budget() # turns the alerts off
299
+ ```
300
+
301
+ The outstanding balance is the platform's own exposure control. Reminder emails go out at 80%,
302
+ 100%, 125%, 150% and 175% of `threshold_cents` and restrict nothing; only at `hard_stop_cents`
303
+ (twice the threshold) are **new** jobs refused with code `outstanding_balance_exceeded` — queued
304
+ and running work still finishes and videos keep playing. Paying lifts the block on the next
305
+ request:
306
+
307
+ ```python
308
+ balance = client.billing.retrieve_outstanding_balance()
309
+ if balance.blocked and balance.settlement_available:
310
+ result = client.billing.settle_outstanding_balance()
311
+ print(result.settlement.invoice_id, result.balance.outstanding_cents) # → 0
312
+ ```
313
+
314
+ Check `settlement_available` before offering a "pay now" action: settlement raises
315
+ `PreconditionError` with code `settlement_unavailable` where the rail is switched off, and
316
+ `nothing_outstanding` when there is nothing to pay. Job creates refused for money reasons carry
317
+ `billing_past_due` (a statement is unpaid) or `outstanding_balance_exceeded` (too much unbilled
318
+ usage) — neither clears by retrying, so back-off is the wrong response to both.
319
+
269
320
  ## Errors
270
321
 
271
322
  All exceptions inherit from `TranscodelyError`:
@@ -85,6 +85,9 @@ client.origins # create / get / list / update / validate / archive
85
85
  client.apps # create / get / list / update / archive / enable_hosting
86
86
  client.api_keys # create / get / list / revoke
87
87
  client.organizations # create / get / list / update / check_slug
88
+ client.billing # list_invoices / retrieve / retrieve_upcoming / retrieve_profile / create_portal_session
89
+ # + retrieve_budget / set_budget / clear_budget
90
+ # + retrieve_outstanding_balance / settle_outstanding_balance
88
91
  client.memberships # list / get / update_role / remove
89
92
  client.users # get_me / get / list / update_me
90
93
  client.health # check
@@ -229,6 +232,54 @@ r2 = {
229
232
 
230
233
  Provide either `account_id` or `endpoint`, never both. `jurisdiction` only applies when `account_id` is set.
231
234
 
235
+ ## Billing
236
+
237
+ Billing settles a whole organization, so `client.billing` needs a dashboard session token for an
238
+ organization **owner** plus the organization it is for — an API key is scoped to one app and is
239
+ rejected outright:
240
+
241
+ ```python
242
+ client = Transcodely("tk_session_token", organization_id="org_f6g7h8i9j0")
243
+
244
+ client.billing.retrieve_upcoming() # what the current period has accrued
245
+ client.billing.list_invoices(limit=12)
246
+ ```
247
+
248
+ Three numbers live here and they are not interchangeable:
249
+
250
+ | Call | What it answers | Enforces? |
251
+ |---|---|---|
252
+ | `billing.retrieve_upcoming()` | What the **current period** has accrued | No |
253
+ | `billing.retrieve_budget()` | Spend against the org's own monthly budget | No — emails only |
254
+ | `billing.retrieve_outstanding_balance()` | What is **unsettled**, incl. earlier periods | Yes, at the hard stop |
255
+ | `apps.set_spend_limit(app_id, eur)` | One app's monthly cap | Yes, at 100% |
256
+
257
+ Budgets notify; limits block:
258
+
259
+ ```python
260
+ client.billing.set_budget(500.0) # emails at 50% / 80% / 100%, restricts nothing
261
+ client.billing.clear_budget() # turns the alerts off
262
+ ```
263
+
264
+ The outstanding balance is the platform's own exposure control. Reminder emails go out at 80%,
265
+ 100%, 125%, 150% and 175% of `threshold_cents` and restrict nothing; only at `hard_stop_cents`
266
+ (twice the threshold) are **new** jobs refused with code `outstanding_balance_exceeded` — queued
267
+ and running work still finishes and videos keep playing. Paying lifts the block on the next
268
+ request:
269
+
270
+ ```python
271
+ balance = client.billing.retrieve_outstanding_balance()
272
+ if balance.blocked and balance.settlement_available:
273
+ result = client.billing.settle_outstanding_balance()
274
+ print(result.settlement.invoice_id, result.balance.outstanding_cents) # → 0
275
+ ```
276
+
277
+ Check `settlement_available` before offering a "pay now" action: settlement raises
278
+ `PreconditionError` with code `settlement_unavailable` where the rail is switched off, and
279
+ `nothing_outstanding` when there is nothing to pay. Job creates refused for money reasons carry
280
+ `billing_past_due` (a statement is unpaid) or `outstanding_balance_exceeded` (too much unbilled
281
+ usage) — neither clears by retrying, so back-off is the wrong response to both.
282
+
232
283
  ## Errors
233
284
 
234
285
  All exceptions inherit from `TranscodelyError`:
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "transcodely"
7
- version = "0.3.6"
7
+ version = "0.3.8"
8
8
  description = "Official Python SDK for the Transcodely video transcoding API"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -55,6 +55,10 @@ from .types import (
55
55
  App,
56
56
  AudioTrackConfig,
57
57
  AutoABRConfig,
58
+ BillingPaymentMethod,
59
+ BillingPortalSession,
60
+ BillingProfile,
61
+ BillingStanding,
58
62
  ChapterPoint,
59
63
  ChapterResult,
60
64
  ClipConfig,
@@ -77,6 +81,7 @@ from .types import (
77
81
  OutputFormat,
78
82
  OutputSpec,
79
83
  OutputStatus,
84
+ PaymentMethodState,
80
85
  Preset,
81
86
  PresetVariant,
82
87
  Resolution,
@@ -127,6 +132,10 @@ __all__ = [
127
132
  "AudioTrackConfig",
128
133
  "AuthenticationError",
129
134
  "AutoABRConfig",
135
+ "BillingPaymentMethod",
136
+ "BillingPortalSession",
137
+ "BillingProfile",
138
+ "BillingStanding",
130
139
  "ChapterPoint",
131
140
  "ChapterResult",
132
141
  "ClipConfig",
@@ -158,6 +167,7 @@ __all__ = [
158
167
  "OutputStatus",
159
168
  "Page",
160
169
  "PageContents",
170
+ "PaymentMethodState",
161
171
  "PermissionError",
162
172
  "PreconditionError",
163
173
  "Preset",
@@ -0,0 +1,266 @@
1
+ """Billing resource — an organization's invoices, budget and outstanding balance."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from .._transport.transport import CallOptions, Transport
6
+ from ..pagination import Page, PageContents
7
+ from ..v1 import billing_pb2
8
+ from ._helpers import assign_pagination
9
+
10
+ _SERVICE = "transcodely.v1.BillingService"
11
+
12
+
13
+ class Billing:
14
+ """An organization's billing statements.
15
+
16
+ Unlike every other resource, billing settles a whole organization rather
17
+ than a single app, so it is **not** available to API-key callers: a key is
18
+ scoped to one app, and there is no app-scoped subset of an invoice worth
19
+ serving. An API key gets a :class:`~transcodely.errors.PermissionError`.
20
+
21
+ Reading invoices needs a dashboard session token for an organization
22
+ **owner**, plus the organization the request is for::
23
+
24
+ client = Transcodely(session_token, organization_id="org_f6g7h8i9j0")
25
+ upcoming = client.billing.retrieve_upcoming()
26
+
27
+ Invoices are generated automatically when a period closes. There is no API
28
+ to create, edit, or delete one — a statement records what happened. The one
29
+ exception is :meth:`settle_outstanding_balance`, which closes the period
30
+ early and produces an ordinary statement for what is owed so far.
31
+
32
+ Monetary amounts are integer minor units (cents) of the invoice currency:
33
+ ``total_cents == 1250`` in EUR is 12.50 EUR. The budget is the exception —
34
+ it is stated in whole EUR as a float, matching how a customer types it.
35
+ """
36
+
37
+ def __init__(self, transport: Transport) -> None:
38
+ self._t = transport
39
+
40
+ def list_invoices(
41
+ self,
42
+ *,
43
+ limit: int | None = None,
44
+ opts: CallOptions | None = None,
45
+ ) -> Page[billing_pb2.Invoice]:
46
+ """Page through finalized invoices, newest period first.
47
+
48
+ Line items are omitted here — use :meth:`retrieve` for one invoice's
49
+ breakdown. A statement still being generated for a just-ended period is
50
+ never returned; for the period currently accruing use
51
+ :meth:`retrieve_upcoming`.
52
+ """
53
+
54
+ def fetch(cursor: str | None) -> PageContents[billing_pb2.Invoice]:
55
+ req = billing_pb2.ListInvoicesRequest()
56
+ assign_pagination(req.pagination, limit=limit, cursor=cursor)
57
+ res = self._t.unary(
58
+ _SERVICE, "ListInvoices", req, billing_pb2.ListInvoicesResponse(), opts
59
+ )
60
+ return PageContents(
61
+ items=list(res.invoices),
62
+ next_cursor=res.pagination.next_cursor or None,
63
+ )
64
+
65
+ return Page(fetch)
66
+
67
+ def retrieve(self, invoice_id: str, opts: CallOptions | None = None) -> billing_pb2.Invoice:
68
+ """Retrieve one invoice by ID (``inv_*``), including its line items."""
69
+ req = billing_pb2.GetInvoiceRequest(id=invoice_id)
70
+ return self._t.unary(
71
+ _SERVICE,
72
+ "GetInvoice",
73
+ req,
74
+ billing_pb2.GetInvoiceResponse(),
75
+ opts,
76
+ ).invoice
77
+
78
+ def retrieve_upcoming(self, opts: CallOptions | None = None) -> billing_pb2.Invoice:
79
+ """Retrieve the statement for the period currently accruing.
80
+
81
+ Computed live from settled jobs rather than stored, so its ``id`` is
82
+ empty, its status is ``draft``, and its totals move as jobs finish. Jobs
83
+ still running are not included at any price — a job is billed only once
84
+ it settles.
85
+ """
86
+ req = billing_pb2.GetUpcomingInvoiceRequest()
87
+ return self._t.unary(
88
+ _SERVICE,
89
+ "GetUpcomingInvoice",
90
+ req,
91
+ billing_pb2.GetUpcomingInvoiceResponse(),
92
+ opts,
93
+ ).invoice
94
+
95
+ def retrieve_profile(self, opts: CallOptions | None = None) -> billing_pb2.BillingProfile:
96
+ """Retrieve the organization's payment standing.
97
+
98
+ Reports whether the payment provider holds a chargeable method, and
99
+ whatever it will say about that method for display. Read-only and
100
+ side-effect free — it never creates provider resources. An organization
101
+ that has never touched billing reports ``PAYMENT_METHOD_STATE_NONE``
102
+ and no payment methods.
103
+
104
+ ``payment_method_state`` is the only reliable signal. A method's
105
+ ``brand`` and ``last4`` are frequently absent even for a working card,
106
+ because the provider does not always expose card metadata; render such
107
+ a method as "Card on file" rather than treating the missing digits as
108
+ an error.
109
+ """
110
+ req = billing_pb2.GetBillingProfileRequest()
111
+ return self._t.unary(
112
+ _SERVICE,
113
+ "GetBillingProfile",
114
+ req,
115
+ billing_pb2.GetBillingProfileResponse(),
116
+ opts,
117
+ ).profile
118
+
119
+ def retrieve_budget(self, opts: CallOptions | None = None) -> billing_pb2.Budget:
120
+ """Retrieve the organization's monthly budget and the spend against it.
121
+
122
+ A budget is the organization's own telemetry and it NEVER enforces
123
+ anything: crossing 100% sends an email and changes nothing else. The
124
+ hard cap is the per-app spend limit
125
+ (:meth:`~transcodely.resources.apps.Apps.set_spend_limit`), which does
126
+ refuse new jobs. Limits block; budgets notify.
127
+
128
+ Always returns a budget: an organization that has never set one gets
129
+ ``amount_eur`` absent with ``spent_eur`` still populated, so the current
130
+ period's spend can be shown before a budget exists.
131
+ """
132
+ req = billing_pb2.GetBudgetRequest()
133
+ return self._t.unary(
134
+ _SERVICE,
135
+ "GetBudget",
136
+ req,
137
+ billing_pb2.GetBudgetResponse(),
138
+ opts,
139
+ ).budget
140
+
141
+ def update_budget(
142
+ self,
143
+ amount_eur: float | None = None,
144
+ opts: CallOptions | None = None,
145
+ ) -> billing_pb2.Budget:
146
+ """Set or clear the organization's monthly budget.
147
+
148
+ Pass ``amount_eur`` (must be > 0) to set the budget, or leave it
149
+ ``None`` to clear it and stop the alert emails. :meth:`set_budget` and
150
+ :meth:`clear_budget` are the ergonomic shorthands.
151
+
152
+ Moving the amount never re-arms an alert step already sent this period —
153
+ read ``notified_steps`` on the returned budget to see which have fired.
154
+ """
155
+ req = billing_pb2.UpdateBudgetRequest()
156
+ if amount_eur is not None:
157
+ # Setting the optional field marks presence; leaving it unset omits
158
+ # it from the request, which the server reads as "clear the budget".
159
+ req.amount_eur = amount_eur
160
+ return self._t.unary(
161
+ _SERVICE,
162
+ "UpdateBudget",
163
+ req,
164
+ billing_pb2.UpdateBudgetResponse(),
165
+ opts,
166
+ ).budget
167
+
168
+ def set_budget(self, amount_eur: float, opts: CallOptions | None = None) -> billing_pb2.Budget:
169
+ """Set the organization's monthly budget in EUR (must be > 0).
170
+
171
+ Alert emails go out as spend crosses each step in ``alert_steps`` (50%,
172
+ 80% and 100% today). Nothing is restricted at any of them.
173
+ """
174
+ return self.update_budget(amount_eur, opts)
175
+
176
+ def clear_budget(self, opts: CallOptions | None = None) -> billing_pb2.Budget:
177
+ """Clear the monthly budget, turning the alert emails off (the default)."""
178
+ return self.update_budget(None, opts)
179
+
180
+ def retrieve_outstanding_balance(
181
+ self, opts: CallOptions | None = None
182
+ ) -> billing_pb2.OutstandingBalance:
183
+ """Retrieve the unsettled balance and the threshold it is measured against.
184
+
185
+ Usage that has been accrued and not yet captured onto a statement,
186
+ across every app in the organization. It is a different number from
187
+ :meth:`retrieve_upcoming` (which is what the *current period* accrued,
188
+ while this also carries anything an earlier period left uncaptured) and
189
+ from ``Budget.spent_eur`` (which keeps counting after an invoice is
190
+ issued, while this drops to zero when one is).
191
+
192
+ Reminder emails go out at 80%, 100%, 125%, 150% and 175% of
193
+ ``threshold_cents`` and restrict nothing. Only at ``hard_stop_cents`` —
194
+ twice the threshold — are new jobs refused, with error code
195
+ ``outstanding_balance_exceeded``; work already queued or running still
196
+ finishes and videos keep playing. ``blocked`` reads the same numbers the
197
+ admission gate reads.
198
+
199
+ Always returns a balance: an organization that owes nothing gets one
200
+ with ``outstanding_cents`` 0 and its threshold still populated.
201
+ """
202
+ req = billing_pb2.GetOutstandingBalanceRequest()
203
+ return self._t.unary(
204
+ _SERVICE,
205
+ "GetOutstandingBalance",
206
+ req,
207
+ billing_pb2.GetOutstandingBalanceResponse(),
208
+ opts,
209
+ ).balance
210
+
211
+ def settle_outstanding_balance(
212
+ self, opts: CallOptions | None = None
213
+ ) -> billing_pb2.SettleOutstandingBalanceResponse:
214
+ """Pay the outstanding balance now, without waiting for the period to end.
215
+
216
+ Closes the current period at this instant and produces a real statement
217
+ for everything owed, which the payment provider then charges. On success
218
+ the balance is zero, any admission block is lifted immediately, and the
219
+ trust tier re-evaluates once the statement is paid.
220
+
221
+ Takes no amount on purpose: the figure is whatever the ledger says at
222
+ the instant the settlement runs, so a stale page cannot pay less than is
223
+ owed and leave the difference looking settled.
224
+
225
+ Returns both the :class:`~transcodely.types.Settlement` and the balance
226
+ as it now stands, so a caller that just paid can render the new state
227
+ without a second request.
228
+
229
+ Raises :class:`~transcodely.errors.PreconditionError` with code
230
+ ``settlement_unavailable`` when the deployment has mid-cycle settlement
231
+ switched off — check ``settlement_available`` on the balance before
232
+ offering a "pay now" action — and ``nothing_outstanding`` when there is
233
+ nothing to pay.
234
+ """
235
+ req = billing_pb2.SettleOutstandingBalanceRequest()
236
+ return self._t.unary(
237
+ _SERVICE,
238
+ "SettleOutstandingBalance",
239
+ req,
240
+ billing_pb2.SettleOutstandingBalanceResponse(),
241
+ opts,
242
+ )
243
+
244
+ def create_portal_session(
245
+ self, opts: CallOptions | None = None
246
+ ) -> billing_pb2.BillingPortalSession:
247
+ """Create a session for the payment provider's hosted billing portal.
248
+
249
+ The portal is where a payment method is added or replaced and where
250
+ receipts live — card details never touch this SDK.
251
+
252
+ The first call for an organization also links it to the payment
253
+ provider, so the returned portal is already attached to this
254
+ organization's billing account. Safe to call repeatedly.
255
+
256
+ The session is single-use and expires; request a fresh one per visit
257
+ rather than storing the URL.
258
+ """
259
+ req = billing_pb2.CreateBillingPortalSessionRequest()
260
+ return self._t.unary(
261
+ _SERVICE,
262
+ "CreateBillingPortalSession",
263
+ req,
264
+ billing_pb2.CreateBillingPortalSessionResponse(),
265
+ opts,
266
+ ).session
@@ -61,9 +61,34 @@ from transcodely.v1.app_pb2 import (
61
61
  # token for an organization owner plus ``organization_id`` — an API key is
62
62
  # scoped to one app, while an invoice settles a whole organization, so API keys
63
63
  # are rejected.
64
+ #
65
+ # ``BillingProfile`` reports whether the payment provider holds a chargeable
66
+ # method; a portal session is the only place a card is added or replaced, so
67
+ # card details never reach this SDK. Branch on ``PaymentMethodState``, not on
68
+ # the card fields — ``brand`` and ``last4`` are frequently absent even for a
69
+ # working card.
70
+ #
71
+ # ``Budget`` and ``OutstandingBalance`` are three different numbers away from
72
+ # each other and from an invoice: a budget notifies and never enforces, an
73
+ # upcoming invoice is what the CURRENT PERIOD has accrued, and an outstanding
74
+ # balance is what is UNSETTLED — the one that decides whether new jobs are
75
+ # admitted.
64
76
  from transcodely.v1.billing_pb2 import (
77
+ BillingPaymentMethod,
78
+ BillingPortalSession,
79
+ BillingProfile,
80
+ Budget,
81
+ CreateBillingPortalSessionRequest,
82
+ CreateBillingPortalSessionResponse,
83
+ ExposureThresholdSource,
84
+ GetBillingProfileRequest,
85
+ GetBillingProfileResponse,
86
+ GetBudgetRequest,
87
+ GetBudgetResponse,
65
88
  GetInvoiceRequest,
66
89
  GetInvoiceResponse,
90
+ GetOutstandingBalanceRequest,
91
+ GetOutstandingBalanceResponse,
67
92
  GetUpcomingInvoiceRequest,
68
93
  GetUpcomingInvoiceResponse,
69
94
  Invoice,
@@ -72,13 +97,36 @@ from transcodely.v1.billing_pb2 import (
72
97
  InvoiceStatus,
73
98
  ListInvoicesRequest,
74
99
  ListInvoicesResponse,
100
+ OutstandingBalance,
101
+ PaymentMethodState,
102
+ Settlement,
103
+ SettleOutstandingBalanceRequest,
104
+ SettleOutstandingBalanceResponse,
105
+ TrustTier,
106
+ UpdateBudgetRequest,
107
+ UpdateBudgetResponse,
75
108
  )
76
109
  from transcodely.v1.codec_av1_pb2 import AV1Options
77
110
  from transcodely.v1.codec_h264_pb2 import H264Options
78
111
  from transcodely.v1.codec_h265_pb2 import H265Options
79
112
  from transcodely.v1.codec_vp9_pb2 import VP9Options
113
+
114
+ # ``BillingStanding`` is ``BillingProfile.standing`` — derived from the
115
+ # organization's billing facts rather than assigned, so there is nothing to set.
116
+ # ``FREE`` and ``DELINQUENT`` both resolve usage limits to the free tier's, but
117
+ # ``DELINQUENT`` still bills; neither is a suspension. An organization exempt
118
+ # from the payment-method requirement always reports ``ACTIVE``.
119
+ #
120
+ # ``BillingTreatment`` is ``Organization.billing_treatment`` — orthogonal to
121
+ # standing, and assigned rather than derived: standing is a fact about payment
122
+ # health, treatment is the decision about whether automation may act on it. It
123
+ # is populated on admin reads only. ``DunningStage`` belongs to the same wave
124
+ # but reaches no message this SDK can receive, so it stays unexported; reach it
125
+ # at ``transcodely.v1.common_pb2.DunningStage`` if you are decoding admin JSON.
80
126
  from transcodely.v1.common_pb2 import (
81
127
  AudioCodec,
128
+ BillingStanding,
129
+ BillingTreatment,
82
130
  BitrateMode,
83
131
  Container,
84
132
  ContentType,
@@ -388,7 +436,13 @@ __all__ = [
388
436
  "AutoABRConfig",
389
437
  "AutoProfileDefaults",
390
438
  "BYOKConfig",
439
+ "BillingPaymentMethod",
440
+ "BillingPortalSession",
441
+ "BillingProfile",
442
+ "BillingStanding",
443
+ "BillingTreatment",
391
444
  "BitrateMode",
445
+ "Budget",
392
446
  "BurnInStyle",
393
447
  "CancelJobRequest",
394
448
  "CancelJobResponse",
@@ -414,6 +468,8 @@ __all__ = [
414
468
  "CreateAPIKeyResponse",
415
469
  "CreateAppRequest",
416
470
  "CreateAppResponse",
471
+ "CreateBillingPortalSessionRequest",
472
+ "CreateBillingPortalSessionResponse",
417
473
  "CreateFromUrlRequest",
418
474
  "CreateFromUrlResponse",
419
475
  "CreateJobRequest",
@@ -450,6 +506,7 @@ __all__ = [
450
506
  "EventRequest",
451
507
  "EventType",
452
508
  "ExecutionTiming",
509
+ "ExposureThresholdSource",
453
510
  "GOPAlignmentMode",
454
511
  "GcsCredentials",
455
512
  "GcsOriginConfig",
@@ -457,6 +514,10 @@ __all__ = [
457
514
  "GetAPIKeyResponse",
458
515
  "GetAppRequest",
459
516
  "GetAppResponse",
517
+ "GetBillingProfileRequest",
518
+ "GetBillingProfileResponse",
519
+ "GetBudgetRequest",
520
+ "GetBudgetResponse",
460
521
  "GetEndpointHealthRequest",
461
522
  "GetEndpointHealthResponse",
462
523
  "GetInvoiceRequest",
@@ -471,6 +532,8 @@ __all__ = [
471
532
  "GetOrganizationResponse",
472
533
  "GetOriginRequest",
473
534
  "GetOriginResponse",
535
+ "GetOutstandingBalanceRequest",
536
+ "GetOutstandingBalanceResponse",
474
537
  "GetPresetBySlugRequest",
475
538
  "GetPresetBySlugResponse",
476
539
  "GetPresetRequest",
@@ -557,8 +620,10 @@ __all__ = [
557
620
  "OutputSpec",
558
621
  "OutputStatus",
559
622
  "OutputVariantResult",
623
+ "OutstandingBalance",
560
624
  "PaginationRequest",
561
625
  "PaginationResponse",
626
+ "PaymentMethodState",
562
627
  "Preset",
563
628
  "PresetVariant",
564
629
  "PricingSnapshot",
@@ -581,6 +646,9 @@ __all__ = [
581
646
  "SegmentConfig",
582
647
  "SendTestWebhookRequest",
583
648
  "SendTestWebhookResponse",
649
+ "SettleOutstandingBalanceRequest",
650
+ "SettleOutstandingBalanceResponse",
651
+ "Settlement",
584
652
  "StreamingConfig",
585
653
  "SubtitleFormat",
586
654
  "SubtitleOperation",
@@ -593,8 +661,11 @@ __all__ = [
593
661
  "ThumbnailSpec",
594
662
  "ToneMapping",
595
663
  "TopVideo",
664
+ "TrustTier",
596
665
  "UpdateAppRequest",
597
666
  "UpdateAppResponse",
667
+ "UpdateBudgetRequest",
668
+ "UpdateBudgetResponse",
598
669
  "UpdateHostingConfigRequest",
599
670
  "UpdateHostingConfigResponse",
600
671
  "UpdateMeRequest",