88import httpx
99
1010from ..._types import Body , Omit , Query , Headers , NotGiven , omit , not_given
11- from ..._utils import path_template , maybe_transform , async_maybe_transform
11+ from ..._utils import path_template , maybe_transform , strip_not_given , async_maybe_transform
1212from ..._compat import cached_property
1313from ..._resource import SyncAPIResource , AsyncAPIResource
1414from ..._response import (
@@ -367,6 +367,7 @@ def send(
367367 rf_partner : str | Omit = omit ,
368368 rf_tag : str | Omit = omit ,
369369 text : str | Omit = omit ,
370+ idempotency_key : str | Omit = omit ,
370371 # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
371372 # The extra values given here take precedence over values defined on the client or passed to this method.
372373 extra_headers : Headers | None = None ,
@@ -377,6 +378,30 @@ def send(
377378 """
378379 Send a new message to a chat.
379380
381+ **Idempotency.** Pass an `Idempotency-Key` header to make retries safe. The
382+ first request with a given key is executed normally and its response is stored
383+ for **24 hours**; any later request with the same key returns that stored
384+ response, plus an `Idempotent-Replayed: true` header, without contacting
385+ OnlyFans and without consuming credits. The replayed body is the original
386+ response with its `_meta._credits` block rewritten to show `used: 0` and your
387+ current balance.
388+
389+ Keys are scoped to your team, this endpoint and the account in the URL, so the
390+ same value can be reused safely against a different account. Use a fresh, unique
391+ value (a UUID works well) for each message you send; it must be 1-255 printable
392+ ASCII characters.
393+
394+ - `400 IDEMPOTENCY_KEY_INVALID` — the header value is empty, too long, or
395+ contains non-ASCII characters.
396+ - `409 IDEMPOTENCY_CONFLICT` — an earlier request with this key is still
397+ running. Retry once it finishes.
398+ - `422 IDEMPOTENCY_KEY_MISMATCH` — this key was already used with a different
399+ request body or chat.
400+
401+ Responses with a `5xx` status (and `408`/`429`) are never stored, so a failed
402+ send can be retried with the same key. The header is optional: omit it and the
403+ endpoint behaves exactly as before.
404+
380405 Args:
381406 block_banned_words: Screen `text` for OnlyFans banned words and block the send if any are found
382407 (returns a 422 listing the offending words). `strict_ban` blocks all tiers,
@@ -421,6 +446,7 @@ def send(
421446 raise ValueError (f"Expected a non-empty value for `account` but received { account !r} " )
422447 if not chat_id :
423448 raise ValueError (f"Expected a non-empty value for `chat_id` but received { chat_id !r} " )
449+ extra_headers = {** strip_not_given ({"Idempotency-Key" : idempotency_key }), ** (extra_headers or {})}
424450 return self ._post (
425451 path_template ("/api/{account}/chats/{chat_id}/messages" , account = account , chat_id = chat_id ),
426452 body = maybe_transform (
@@ -869,6 +895,7 @@ async def send(
869895 rf_partner : str | Omit = omit ,
870896 rf_tag : str | Omit = omit ,
871897 text : str | Omit = omit ,
898+ idempotency_key : str | Omit = omit ,
872899 # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
873900 # The extra values given here take precedence over values defined on the client or passed to this method.
874901 extra_headers : Headers | None = None ,
@@ -879,6 +906,30 @@ async def send(
879906 """
880907 Send a new message to a chat.
881908
909+ **Idempotency.** Pass an `Idempotency-Key` header to make retries safe. The
910+ first request with a given key is executed normally and its response is stored
911+ for **24 hours**; any later request with the same key returns that stored
912+ response, plus an `Idempotent-Replayed: true` header, without contacting
913+ OnlyFans and without consuming credits. The replayed body is the original
914+ response with its `_meta._credits` block rewritten to show `used: 0` and your
915+ current balance.
916+
917+ Keys are scoped to your team, this endpoint and the account in the URL, so the
918+ same value can be reused safely against a different account. Use a fresh, unique
919+ value (a UUID works well) for each message you send; it must be 1-255 printable
920+ ASCII characters.
921+
922+ - `400 IDEMPOTENCY_KEY_INVALID` — the header value is empty, too long, or
923+ contains non-ASCII characters.
924+ - `409 IDEMPOTENCY_CONFLICT` — an earlier request with this key is still
925+ running. Retry once it finishes.
926+ - `422 IDEMPOTENCY_KEY_MISMATCH` — this key was already used with a different
927+ request body or chat.
928+
929+ Responses with a `5xx` status (and `408`/`429`) are never stored, so a failed
930+ send can be retried with the same key. The header is optional: omit it and the
931+ endpoint behaves exactly as before.
932+
882933 Args:
883934 block_banned_words: Screen `text` for OnlyFans banned words and block the send if any are found
884935 (returns a 422 listing the offending words). `strict_ban` blocks all tiers,
@@ -923,6 +974,7 @@ async def send(
923974 raise ValueError (f"Expected a non-empty value for `account` but received { account !r} " )
924975 if not chat_id :
925976 raise ValueError (f"Expected a non-empty value for `chat_id` but received { chat_id !r} " )
977+ extra_headers = {** strip_not_given ({"Idempotency-Key" : idempotency_key }), ** (extra_headers or {})}
926978 return await self ._post (
927979 path_template ("/api/{account}/chats/{chat_id}/messages" , account = account , chat_id = chat_id ),
928980 body = await async_maybe_transform (
0 commit comments