postproxy-sdk 1.0.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.
- postproxy_sdk-1.0.0/.gitignore +9 -0
- postproxy_sdk-1.0.0/PKG-INFO +263 -0
- postproxy_sdk-1.0.0/README.md +246 -0
- postproxy_sdk-1.0.0/examples/create_post.py +162 -0
- postproxy_sdk-1.0.0/examples/initialize_connection.py +37 -0
- postproxy_sdk-1.0.0/postproxy/__init__.py +90 -0
- postproxy_sdk-1.0.0/postproxy/_client.py +111 -0
- postproxy_sdk-1.0.0/postproxy/_constants.py +45 -0
- postproxy_sdk-1.0.0/postproxy/_exceptions.py +32 -0
- postproxy_sdk-1.0.0/postproxy/_types.py +164 -0
- postproxy_sdk-1.0.0/postproxy/resources/__init__.py +5 -0
- postproxy_sdk-1.0.0/postproxy/resources/posts.py +162 -0
- postproxy_sdk-1.0.0/postproxy/resources/profile_groups.py +44 -0
- postproxy_sdk-1.0.0/postproxy/resources/profiles.py +51 -0
- postproxy_sdk-1.0.0/pyproject.toml +30 -0
- postproxy_sdk-1.0.0/tests/__init__.py +0 -0
- postproxy_sdk-1.0.0/tests/conftest.py +39 -0
- postproxy_sdk-1.0.0/tests/test_client.py +85 -0
- postproxy_sdk-1.0.0/tests/test_posts.py +115 -0
- postproxy_sdk-1.0.0/tests/test_profile_groups.py +62 -0
- postproxy_sdk-1.0.0/tests/test_profiles.py +64 -0
- postproxy_sdk-1.0.0/tests/test_types.py +105 -0
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: postproxy-sdk
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Async Python client for the PostProxy API
|
|
5
|
+
Project-URL: Homepage, https://postproxy.dev
|
|
6
|
+
Project-URL: Documentation, https://postproxy.dev/getting-started/overview/
|
|
7
|
+
Project-URL: Repository, https://github.com/postproxy/postproxy-python
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
Requires-Python: >=3.10
|
|
10
|
+
Requires-Dist: httpx>=0.25.0
|
|
11
|
+
Requires-Dist: pydantic>=2.0
|
|
12
|
+
Provides-Extra: dev
|
|
13
|
+
Requires-Dist: mypy>=1.19; extra == 'dev'
|
|
14
|
+
Requires-Dist: pytest-asyncio>=1.3.0; extra == 'dev'
|
|
15
|
+
Requires-Dist: pytest>=9.0; extra == 'dev'
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
|
|
18
|
+
# PostProxy Python SDK
|
|
19
|
+
|
|
20
|
+
Async Python client for the [PostProxy API](https://postproxy.dev). Fully typed with Pydantic v2 models and async/await via httpx.
|
|
21
|
+
|
|
22
|
+
## Installation
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
pip install postproxy-sdk
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Requires Python 3.10+.
|
|
29
|
+
|
|
30
|
+
## Quick start
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
import asyncio
|
|
34
|
+
from postproxy import PostProxy
|
|
35
|
+
|
|
36
|
+
async def main():
|
|
37
|
+
async with PostProxy("your-api-key", profile_group_id="pg-abc") as client:
|
|
38
|
+
# List profiles
|
|
39
|
+
profiles = await client.profiles.list()
|
|
40
|
+
|
|
41
|
+
# Create a post
|
|
42
|
+
post = await client.posts.create(
|
|
43
|
+
"Hello from PostProxy!",
|
|
44
|
+
profiles=[profiles[0].id],
|
|
45
|
+
)
|
|
46
|
+
print(post.id, post.status)
|
|
47
|
+
|
|
48
|
+
asyncio.run(main())
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Usage
|
|
52
|
+
|
|
53
|
+
### Client
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
from postproxy import PostProxy
|
|
57
|
+
|
|
58
|
+
# Basic
|
|
59
|
+
client = PostProxy("your-api-key")
|
|
60
|
+
|
|
61
|
+
# With a default profile group (applied to all requests)
|
|
62
|
+
client = PostProxy("your-api-key", profile_group_id="pg-abc")
|
|
63
|
+
|
|
64
|
+
# With a custom httpx client
|
|
65
|
+
import httpx
|
|
66
|
+
client = PostProxy("your-api-key", httpx_client=httpx.AsyncClient(timeout=30))
|
|
67
|
+
|
|
68
|
+
# As a context manager (auto-closes the HTTP client)
|
|
69
|
+
async with PostProxy("your-api-key") as client:
|
|
70
|
+
...
|
|
71
|
+
|
|
72
|
+
# Manual cleanup
|
|
73
|
+
await client.close()
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Posts
|
|
77
|
+
|
|
78
|
+
```python
|
|
79
|
+
# List posts (paginated)
|
|
80
|
+
page = await client.posts.list(page=0, per_page=10, status="draft")
|
|
81
|
+
print(page.total, page.data)
|
|
82
|
+
|
|
83
|
+
# Filter by platform and schedule
|
|
84
|
+
from datetime import datetime
|
|
85
|
+
page = await client.posts.list(
|
|
86
|
+
platforms=["instagram", "tiktok"],
|
|
87
|
+
scheduled_after=datetime(2025, 6, 1),
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
# Get a single post
|
|
91
|
+
post = await client.posts.get("post-id")
|
|
92
|
+
|
|
93
|
+
# Create a post
|
|
94
|
+
post = await client.posts.create(
|
|
95
|
+
"Check out our new product!",
|
|
96
|
+
profiles=["profile-id-1", "profile-id-2"],
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
# Create a draft
|
|
100
|
+
post = await client.posts.create(
|
|
101
|
+
"Draft content",
|
|
102
|
+
profiles=["profile-id"],
|
|
103
|
+
draft=True,
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
# Create with media URLs
|
|
107
|
+
post = await client.posts.create(
|
|
108
|
+
"Photo post",
|
|
109
|
+
profiles=["profile-id"],
|
|
110
|
+
media=["https://example.com/image.jpg"],
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
# Create with local file uploads
|
|
114
|
+
post = await client.posts.create(
|
|
115
|
+
"Posted with a local file!",
|
|
116
|
+
profiles=["profile-id"],
|
|
117
|
+
media_files=["./photo.jpg", "./video.mp4"],
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
# Mix media URLs and local files
|
|
121
|
+
post = await client.posts.create(
|
|
122
|
+
"Mixed media",
|
|
123
|
+
profiles=["profile-id"],
|
|
124
|
+
media=["https://example.com/image.jpg"],
|
|
125
|
+
media_files=["./local-photo.jpg"],
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
# Create with platform-specific params
|
|
129
|
+
from postproxy import PlatformParams, InstagramParams, TikTokParams
|
|
130
|
+
|
|
131
|
+
post = await client.posts.create(
|
|
132
|
+
"Cross-platform post",
|
|
133
|
+
profiles=["ig-profile", "tt-profile"],
|
|
134
|
+
platforms=PlatformParams(
|
|
135
|
+
instagram=InstagramParams(format="reel", collaborators=["@friend"]),
|
|
136
|
+
tiktok=TikTokParams(format="video", privacy_status="PUBLIC_TO_EVERYONE"),
|
|
137
|
+
),
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
# Schedule a post
|
|
141
|
+
post = await client.posts.create(
|
|
142
|
+
"Scheduled post",
|
|
143
|
+
profiles=["profile-id"],
|
|
144
|
+
scheduled_at="2025-12-25T09:00:00Z",
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
# Publish a draft
|
|
148
|
+
post = await client.posts.publish_draft("post-id")
|
|
149
|
+
|
|
150
|
+
# Delete a post
|
|
151
|
+
result = await client.posts.delete("post-id")
|
|
152
|
+
print(result.deleted) # True
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### Profiles
|
|
156
|
+
|
|
157
|
+
```python
|
|
158
|
+
# List all profiles
|
|
159
|
+
profiles = await client.profiles.list()
|
|
160
|
+
|
|
161
|
+
# List profiles in a specific group (overrides client default)
|
|
162
|
+
profiles = await client.profiles.list(profile_group_id="pg-other")
|
|
163
|
+
|
|
164
|
+
# Get a single profile
|
|
165
|
+
profile = await client.profiles.get("profile-id")
|
|
166
|
+
print(profile.name, profile.platform, profile.status)
|
|
167
|
+
|
|
168
|
+
# Get available placements for a profile
|
|
169
|
+
placements = await client.profiles.placements("profile-id")
|
|
170
|
+
for p in placements:
|
|
171
|
+
print(p.id, p.name)
|
|
172
|
+
|
|
173
|
+
# Delete a profile
|
|
174
|
+
result = await client.profiles.delete("profile-id")
|
|
175
|
+
print(result.success) # True
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### Profile Groups
|
|
179
|
+
|
|
180
|
+
```python
|
|
181
|
+
# List all groups
|
|
182
|
+
groups = await client.profile_groups.list()
|
|
183
|
+
|
|
184
|
+
# Get a single group
|
|
185
|
+
group = await client.profile_groups.get("pg-id")
|
|
186
|
+
print(group.name, group.profiles_count)
|
|
187
|
+
|
|
188
|
+
# Create a group
|
|
189
|
+
group = await client.profile_groups.create("My New Group")
|
|
190
|
+
|
|
191
|
+
# Delete a group (must have no profiles)
|
|
192
|
+
result = await client.profile_groups.delete("pg-id")
|
|
193
|
+
print(result.deleted) # True
|
|
194
|
+
|
|
195
|
+
# Initialize a social platform connection
|
|
196
|
+
conn = await client.profile_groups.initialize_connection(
|
|
197
|
+
"pg-id",
|
|
198
|
+
platform="instagram",
|
|
199
|
+
redirect_url="https://yourapp.com/callback",
|
|
200
|
+
)
|
|
201
|
+
print(conn.url) # Redirect the user to this URL
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
## Error handling
|
|
205
|
+
|
|
206
|
+
All errors extend `PostProxyError`, which includes the HTTP status code and raw response body:
|
|
207
|
+
|
|
208
|
+
```python
|
|
209
|
+
from postproxy import (
|
|
210
|
+
PostProxyError,
|
|
211
|
+
AuthenticationError, # 401
|
|
212
|
+
BadRequestError, # 400
|
|
213
|
+
NotFoundError, # 404
|
|
214
|
+
ValidationError, # 422
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
try:
|
|
218
|
+
await client.posts.get("nonexistent")
|
|
219
|
+
except NotFoundError as e:
|
|
220
|
+
print(e.status_code) # 404
|
|
221
|
+
print(e.response) # {"error": "Not found"}
|
|
222
|
+
except PostProxyError as e:
|
|
223
|
+
print(f"API error {e.status_code}: {e}")
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
## Types
|
|
227
|
+
|
|
228
|
+
All responses are parsed into Pydantic v2 models. Key types:
|
|
229
|
+
|
|
230
|
+
| Model | Fields |
|
|
231
|
+
|---|---|
|
|
232
|
+
| `Post` | id, body, status, scheduled_at, created_at, platforms |
|
|
233
|
+
| `Profile` | id, name, status, platform, profile_group_id, expires_at, post_count |
|
|
234
|
+
| `ProfileGroup` | id, name, profiles_count |
|
|
235
|
+
| `PlatformResult` | platform, status, params, error, attempted_at, insights |
|
|
236
|
+
| `PaginatedResponse[T]` | total, page, per_page, data |
|
|
237
|
+
|
|
238
|
+
### Platform parameter models
|
|
239
|
+
|
|
240
|
+
| Model | Platform |
|
|
241
|
+
|---|---|
|
|
242
|
+
| `FacebookParams` | format (`post`, `story`), first_comment, page_id |
|
|
243
|
+
| `InstagramParams` | format (`post`, `reel`, `story`), first_comment, collaborators, cover_url, audio_name, trial_strategy, thumb_offset |
|
|
244
|
+
| `TikTokParams` | format (`video`, `image`), privacy_status, photo_cover_index, auto_add_music, made_with_ai, disable_comment, disable_duet, disable_stitch, brand_content_toggle, brand_organic_toggle |
|
|
245
|
+
| `LinkedInParams` | format (`post`), organization_id |
|
|
246
|
+
| `YouTubeParams` | format (`post`), title, privacy_status, cover_url |
|
|
247
|
+
| `PinterestParams` | format (`pin`), title, board_id, destination_link, cover_url, thumb_offset |
|
|
248
|
+
| `ThreadsParams` | format (`post`) |
|
|
249
|
+
| `TwitterParams` | format (`post`) |
|
|
250
|
+
|
|
251
|
+
Wrap them in `PlatformParams` when passing to `posts.create()`.
|
|
252
|
+
|
|
253
|
+
## Development
|
|
254
|
+
|
|
255
|
+
```bash
|
|
256
|
+
pip install -e ".[dev]"
|
|
257
|
+
pytest
|
|
258
|
+
mypy postproxy/
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
## License
|
|
262
|
+
|
|
263
|
+
MIT
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
# PostProxy Python SDK
|
|
2
|
+
|
|
3
|
+
Async Python client for the [PostProxy API](https://postproxy.dev). Fully typed with Pydantic v2 models and async/await via httpx.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install postproxy-sdk
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Requires Python 3.10+.
|
|
12
|
+
|
|
13
|
+
## Quick start
|
|
14
|
+
|
|
15
|
+
```python
|
|
16
|
+
import asyncio
|
|
17
|
+
from postproxy import PostProxy
|
|
18
|
+
|
|
19
|
+
async def main():
|
|
20
|
+
async with PostProxy("your-api-key", profile_group_id="pg-abc") as client:
|
|
21
|
+
# List profiles
|
|
22
|
+
profiles = await client.profiles.list()
|
|
23
|
+
|
|
24
|
+
# Create a post
|
|
25
|
+
post = await client.posts.create(
|
|
26
|
+
"Hello from PostProxy!",
|
|
27
|
+
profiles=[profiles[0].id],
|
|
28
|
+
)
|
|
29
|
+
print(post.id, post.status)
|
|
30
|
+
|
|
31
|
+
asyncio.run(main())
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Usage
|
|
35
|
+
|
|
36
|
+
### Client
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
from postproxy import PostProxy
|
|
40
|
+
|
|
41
|
+
# Basic
|
|
42
|
+
client = PostProxy("your-api-key")
|
|
43
|
+
|
|
44
|
+
# With a default profile group (applied to all requests)
|
|
45
|
+
client = PostProxy("your-api-key", profile_group_id="pg-abc")
|
|
46
|
+
|
|
47
|
+
# With a custom httpx client
|
|
48
|
+
import httpx
|
|
49
|
+
client = PostProxy("your-api-key", httpx_client=httpx.AsyncClient(timeout=30))
|
|
50
|
+
|
|
51
|
+
# As a context manager (auto-closes the HTTP client)
|
|
52
|
+
async with PostProxy("your-api-key") as client:
|
|
53
|
+
...
|
|
54
|
+
|
|
55
|
+
# Manual cleanup
|
|
56
|
+
await client.close()
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Posts
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
# List posts (paginated)
|
|
63
|
+
page = await client.posts.list(page=0, per_page=10, status="draft")
|
|
64
|
+
print(page.total, page.data)
|
|
65
|
+
|
|
66
|
+
# Filter by platform and schedule
|
|
67
|
+
from datetime import datetime
|
|
68
|
+
page = await client.posts.list(
|
|
69
|
+
platforms=["instagram", "tiktok"],
|
|
70
|
+
scheduled_after=datetime(2025, 6, 1),
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
# Get a single post
|
|
74
|
+
post = await client.posts.get("post-id")
|
|
75
|
+
|
|
76
|
+
# Create a post
|
|
77
|
+
post = await client.posts.create(
|
|
78
|
+
"Check out our new product!",
|
|
79
|
+
profiles=["profile-id-1", "profile-id-2"],
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
# Create a draft
|
|
83
|
+
post = await client.posts.create(
|
|
84
|
+
"Draft content",
|
|
85
|
+
profiles=["profile-id"],
|
|
86
|
+
draft=True,
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
# Create with media URLs
|
|
90
|
+
post = await client.posts.create(
|
|
91
|
+
"Photo post",
|
|
92
|
+
profiles=["profile-id"],
|
|
93
|
+
media=["https://example.com/image.jpg"],
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
# Create with local file uploads
|
|
97
|
+
post = await client.posts.create(
|
|
98
|
+
"Posted with a local file!",
|
|
99
|
+
profiles=["profile-id"],
|
|
100
|
+
media_files=["./photo.jpg", "./video.mp4"],
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
# Mix media URLs and local files
|
|
104
|
+
post = await client.posts.create(
|
|
105
|
+
"Mixed media",
|
|
106
|
+
profiles=["profile-id"],
|
|
107
|
+
media=["https://example.com/image.jpg"],
|
|
108
|
+
media_files=["./local-photo.jpg"],
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
# Create with platform-specific params
|
|
112
|
+
from postproxy import PlatformParams, InstagramParams, TikTokParams
|
|
113
|
+
|
|
114
|
+
post = await client.posts.create(
|
|
115
|
+
"Cross-platform post",
|
|
116
|
+
profiles=["ig-profile", "tt-profile"],
|
|
117
|
+
platforms=PlatformParams(
|
|
118
|
+
instagram=InstagramParams(format="reel", collaborators=["@friend"]),
|
|
119
|
+
tiktok=TikTokParams(format="video", privacy_status="PUBLIC_TO_EVERYONE"),
|
|
120
|
+
),
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
# Schedule a post
|
|
124
|
+
post = await client.posts.create(
|
|
125
|
+
"Scheduled post",
|
|
126
|
+
profiles=["profile-id"],
|
|
127
|
+
scheduled_at="2025-12-25T09:00:00Z",
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
# Publish a draft
|
|
131
|
+
post = await client.posts.publish_draft("post-id")
|
|
132
|
+
|
|
133
|
+
# Delete a post
|
|
134
|
+
result = await client.posts.delete("post-id")
|
|
135
|
+
print(result.deleted) # True
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Profiles
|
|
139
|
+
|
|
140
|
+
```python
|
|
141
|
+
# List all profiles
|
|
142
|
+
profiles = await client.profiles.list()
|
|
143
|
+
|
|
144
|
+
# List profiles in a specific group (overrides client default)
|
|
145
|
+
profiles = await client.profiles.list(profile_group_id="pg-other")
|
|
146
|
+
|
|
147
|
+
# Get a single profile
|
|
148
|
+
profile = await client.profiles.get("profile-id")
|
|
149
|
+
print(profile.name, profile.platform, profile.status)
|
|
150
|
+
|
|
151
|
+
# Get available placements for a profile
|
|
152
|
+
placements = await client.profiles.placements("profile-id")
|
|
153
|
+
for p in placements:
|
|
154
|
+
print(p.id, p.name)
|
|
155
|
+
|
|
156
|
+
# Delete a profile
|
|
157
|
+
result = await client.profiles.delete("profile-id")
|
|
158
|
+
print(result.success) # True
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### Profile Groups
|
|
162
|
+
|
|
163
|
+
```python
|
|
164
|
+
# List all groups
|
|
165
|
+
groups = await client.profile_groups.list()
|
|
166
|
+
|
|
167
|
+
# Get a single group
|
|
168
|
+
group = await client.profile_groups.get("pg-id")
|
|
169
|
+
print(group.name, group.profiles_count)
|
|
170
|
+
|
|
171
|
+
# Create a group
|
|
172
|
+
group = await client.profile_groups.create("My New Group")
|
|
173
|
+
|
|
174
|
+
# Delete a group (must have no profiles)
|
|
175
|
+
result = await client.profile_groups.delete("pg-id")
|
|
176
|
+
print(result.deleted) # True
|
|
177
|
+
|
|
178
|
+
# Initialize a social platform connection
|
|
179
|
+
conn = await client.profile_groups.initialize_connection(
|
|
180
|
+
"pg-id",
|
|
181
|
+
platform="instagram",
|
|
182
|
+
redirect_url="https://yourapp.com/callback",
|
|
183
|
+
)
|
|
184
|
+
print(conn.url) # Redirect the user to this URL
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
## Error handling
|
|
188
|
+
|
|
189
|
+
All errors extend `PostProxyError`, which includes the HTTP status code and raw response body:
|
|
190
|
+
|
|
191
|
+
```python
|
|
192
|
+
from postproxy import (
|
|
193
|
+
PostProxyError,
|
|
194
|
+
AuthenticationError, # 401
|
|
195
|
+
BadRequestError, # 400
|
|
196
|
+
NotFoundError, # 404
|
|
197
|
+
ValidationError, # 422
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
try:
|
|
201
|
+
await client.posts.get("nonexistent")
|
|
202
|
+
except NotFoundError as e:
|
|
203
|
+
print(e.status_code) # 404
|
|
204
|
+
print(e.response) # {"error": "Not found"}
|
|
205
|
+
except PostProxyError as e:
|
|
206
|
+
print(f"API error {e.status_code}: {e}")
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
## Types
|
|
210
|
+
|
|
211
|
+
All responses are parsed into Pydantic v2 models. Key types:
|
|
212
|
+
|
|
213
|
+
| Model | Fields |
|
|
214
|
+
|---|---|
|
|
215
|
+
| `Post` | id, body, status, scheduled_at, created_at, platforms |
|
|
216
|
+
| `Profile` | id, name, status, platform, profile_group_id, expires_at, post_count |
|
|
217
|
+
| `ProfileGroup` | id, name, profiles_count |
|
|
218
|
+
| `PlatformResult` | platform, status, params, error, attempted_at, insights |
|
|
219
|
+
| `PaginatedResponse[T]` | total, page, per_page, data |
|
|
220
|
+
|
|
221
|
+
### Platform parameter models
|
|
222
|
+
|
|
223
|
+
| Model | Platform |
|
|
224
|
+
|---|---|
|
|
225
|
+
| `FacebookParams` | format (`post`, `story`), first_comment, page_id |
|
|
226
|
+
| `InstagramParams` | format (`post`, `reel`, `story`), first_comment, collaborators, cover_url, audio_name, trial_strategy, thumb_offset |
|
|
227
|
+
| `TikTokParams` | format (`video`, `image`), privacy_status, photo_cover_index, auto_add_music, made_with_ai, disable_comment, disable_duet, disable_stitch, brand_content_toggle, brand_organic_toggle |
|
|
228
|
+
| `LinkedInParams` | format (`post`), organization_id |
|
|
229
|
+
| `YouTubeParams` | format (`post`), title, privacy_status, cover_url |
|
|
230
|
+
| `PinterestParams` | format (`pin`), title, board_id, destination_link, cover_url, thumb_offset |
|
|
231
|
+
| `ThreadsParams` | format (`post`) |
|
|
232
|
+
| `TwitterParams` | format (`post`) |
|
|
233
|
+
|
|
234
|
+
Wrap them in `PlatformParams` when passing to `posts.create()`.
|
|
235
|
+
|
|
236
|
+
## Development
|
|
237
|
+
|
|
238
|
+
```bash
|
|
239
|
+
pip install -e ".[dev]"
|
|
240
|
+
pytest
|
|
241
|
+
mypy postproxy/
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
## License
|
|
245
|
+
|
|
246
|
+
MIT
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
"""Examples of creating posts with the PostProxy SDK."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import os
|
|
5
|
+
|
|
6
|
+
from postproxy import (
|
|
7
|
+
PostProxy,
|
|
8
|
+
PlatformParams,
|
|
9
|
+
FacebookParams,
|
|
10
|
+
InstagramParams,
|
|
11
|
+
TikTokParams,
|
|
12
|
+
YouTubeParams,
|
|
13
|
+
PinterestParams,
|
|
14
|
+
LinkedInParams,
|
|
15
|
+
ThreadsParams,
|
|
16
|
+
TwitterParams,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
API_KEY = os.environ["POSTPROXY_API_KEY"]
|
|
20
|
+
PROFILE_GROUP_ID = os.environ.get("POSTPROXY_PROFILE_GROUP_ID")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
async def simple_post():
|
|
24
|
+
"""Create a simple text post."""
|
|
25
|
+
async with PostProxy(API_KEY, profile_group_id=PROFILE_GROUP_ID) as client:
|
|
26
|
+
profiles = await client.profiles.list()
|
|
27
|
+
post = await client.posts.create(
|
|
28
|
+
"Hello from PostProxy Python SDK!",
|
|
29
|
+
profiles=[profiles[0].id],
|
|
30
|
+
)
|
|
31
|
+
print(f"Created post {post.id} — status: {post.status}")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
async def post_with_media():
|
|
35
|
+
"""Create a post with media URLs."""
|
|
36
|
+
async with PostProxy(API_KEY, profile_group_id=PROFILE_GROUP_ID) as client:
|
|
37
|
+
profiles = await client.profiles.list()
|
|
38
|
+
facebook_profile = next(
|
|
39
|
+
(p for p in profiles if p.platform == "facebook"), None
|
|
40
|
+
)
|
|
41
|
+
if facebook_profile is None:
|
|
42
|
+
raise ValueError("No Facebook profile found")
|
|
43
|
+
|
|
44
|
+
placements = await client.profiles.placements(id=facebook_profile.id)
|
|
45
|
+
|
|
46
|
+
post = await client.posts.create(
|
|
47
|
+
"Check this out!",
|
|
48
|
+
profiles=[facebook_profile.id],
|
|
49
|
+
draft=True,
|
|
50
|
+
media=[
|
|
51
|
+
"https://example.com/photo.jpg",
|
|
52
|
+
],
|
|
53
|
+
platforms=PlatformParams(
|
|
54
|
+
facebook=FacebookParams(
|
|
55
|
+
format="post",
|
|
56
|
+
page_id=placements[0].id,
|
|
57
|
+
first_comment="First!",
|
|
58
|
+
),
|
|
59
|
+
),
|
|
60
|
+
)
|
|
61
|
+
print(f"Created post {post.id} with media — status: {post.status}")
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
async def post_with_local_file():
|
|
65
|
+
"""Create a post with a local file upload."""
|
|
66
|
+
async with PostProxy(API_KEY, profile_group_id=PROFILE_GROUP_ID) as client:
|
|
67
|
+
profiles = await client.profiles.list()
|
|
68
|
+
post = await client.posts.create(
|
|
69
|
+
"Posted with a local file!",
|
|
70
|
+
profiles=[profiles[0].id],
|
|
71
|
+
draft=True,
|
|
72
|
+
media_files=["./photo.jpg"],
|
|
73
|
+
)
|
|
74
|
+
print(f"Created post {post.id} with file upload — status: {post.status}")
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
async def draft_then_publish():
|
|
78
|
+
"""Create a draft post, then publish it."""
|
|
79
|
+
async with PostProxy(API_KEY, profile_group_id=PROFILE_GROUP_ID) as client:
|
|
80
|
+
profiles = await client.profiles.list()
|
|
81
|
+
|
|
82
|
+
# Create as draft
|
|
83
|
+
post = await client.posts.create(
|
|
84
|
+
"This starts as a draft",
|
|
85
|
+
profiles=[profiles[0].id],
|
|
86
|
+
draft=True,
|
|
87
|
+
)
|
|
88
|
+
print(f"Created draft {post.id} — status: {post.status}")
|
|
89
|
+
|
|
90
|
+
# Publish the draft
|
|
91
|
+
post = await client.posts.publish_draft(post.id)
|
|
92
|
+
print(f"Published {post.id} — status: {post.status}")
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
async def scheduled_post():
|
|
96
|
+
"""Schedule a post for the future."""
|
|
97
|
+
async with PostProxy(API_KEY, profile_group_id=PROFILE_GROUP_ID) as client:
|
|
98
|
+
profiles = await client.profiles.list()
|
|
99
|
+
post = await client.posts.create(
|
|
100
|
+
"This post is scheduled!",
|
|
101
|
+
profiles=[profiles[0].id],
|
|
102
|
+
scheduled_at="2025-12-25T09:00:00Z",
|
|
103
|
+
)
|
|
104
|
+
print(f"Scheduled post {post.id} for {post.scheduled_at}")
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
async def cross_platform_post():
|
|
108
|
+
"""Create a post with platform-specific parameters."""
|
|
109
|
+
async with PostProxy(API_KEY, profile_group_id=PROFILE_GROUP_ID) as client:
|
|
110
|
+
profiles = await client.profiles.list()
|
|
111
|
+
|
|
112
|
+
profile_ids = [p.id for p in profiles]
|
|
113
|
+
|
|
114
|
+
post = await client.posts.create(
|
|
115
|
+
"Cross-platform post with custom settings per network",
|
|
116
|
+
profiles=profile_ids,
|
|
117
|
+
draft=True,
|
|
118
|
+
media=["https://example.com/video.mp4"],
|
|
119
|
+
platforms=PlatformParams(
|
|
120
|
+
facebook=FacebookParams(
|
|
121
|
+
format="post",
|
|
122
|
+
first_comment="First!",
|
|
123
|
+
),
|
|
124
|
+
instagram=InstagramParams(
|
|
125
|
+
format="reel",
|
|
126
|
+
collaborators=["@collab_account"],
|
|
127
|
+
),
|
|
128
|
+
tiktok=TikTokParams(
|
|
129
|
+
format="video",
|
|
130
|
+
privacy_status="PUBLIC_TO_EVERYONE",
|
|
131
|
+
auto_add_music=True,
|
|
132
|
+
),
|
|
133
|
+
linkedin=LinkedInParams(
|
|
134
|
+
format="post",
|
|
135
|
+
organization_id="org-123",
|
|
136
|
+
),
|
|
137
|
+
youtube=YouTubeParams(
|
|
138
|
+
format="post",
|
|
139
|
+
title="My Video Title",
|
|
140
|
+
privacy_status="public",
|
|
141
|
+
),
|
|
142
|
+
pinterest=PinterestParams(
|
|
143
|
+
format="pin",
|
|
144
|
+
title="Pin Title",
|
|
145
|
+
board_id="board-abc",
|
|
146
|
+
destination_link="https://example.com",
|
|
147
|
+
),
|
|
148
|
+
threads=ThreadsParams(
|
|
149
|
+
format="post",
|
|
150
|
+
),
|
|
151
|
+
twitter=TwitterParams(
|
|
152
|
+
format="post",
|
|
153
|
+
),
|
|
154
|
+
),
|
|
155
|
+
)
|
|
156
|
+
print(f"Created cross-platform post {post.id}")
|
|
157
|
+
for p in post.platforms:
|
|
158
|
+
print(f" {p.platform}: {p.status}")
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
if __name__ == "__main__":
|
|
162
|
+
asyncio.run(simple_post())
|