storeshot 0.1.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.
- storeshot-0.1.0/.gitignore +8 -0
- storeshot-0.1.0/CHANGELOG.md +7 -0
- storeshot-0.1.0/LICENSE +202 -0
- storeshot-0.1.0/NOTICE +4 -0
- storeshot-0.1.0/PKG-INFO +382 -0
- storeshot-0.1.0/README.md +352 -0
- storeshot-0.1.0/pyproject.toml +89 -0
- storeshot-0.1.0/src/storeshot/__init__.py +41 -0
- storeshot-0.1.0/src/storeshot/_client.py +240 -0
- storeshot-0.1.0/src/storeshot/_compat.py +19 -0
- storeshot-0.1.0/src/storeshot/_core/__init__.py +1 -0
- storeshot-0.1.0/src/storeshot/_core/clock.py +26 -0
- storeshot-0.1.0/src/storeshot/_core/headers.py +67 -0
- storeshot-0.1.0/src/storeshot/_core/operation.py +23 -0
- storeshot-0.1.0/src/storeshot/_core/options.py +31 -0
- storeshot-0.1.0/src/storeshot/_core/redact.py +38 -0
- storeshot-0.1.0/src/storeshot/_core/request.py +316 -0
- storeshot-0.1.0/src/storeshot/_core/retry.py +99 -0
- storeshot-0.1.0/src/storeshot/_errors.py +153 -0
- storeshot-0.1.0/src/storeshot/_generated/__init__.py +1 -0
- storeshot-0.1.0/src/storeshot/_generated/operations.py +833 -0
- storeshot-0.1.0/src/storeshot/_generated/schemas.py +1981 -0
- storeshot-0.1.0/src/storeshot/_helpers/__init__.py +1 -0
- storeshot-0.1.0/src/storeshot/_helpers/archive.py +61 -0
- storeshot-0.1.0/src/storeshot/_helpers/files.py +49 -0
- storeshot-0.1.0/src/storeshot/_helpers/pagination.py +29 -0
- storeshot-0.1.0/src/storeshot/_helpers/wait.py +82 -0
- storeshot-0.1.0/src/storeshot/_operation_map.py +91 -0
- storeshot-0.1.0/src/storeshot/_resources/__init__.py +1 -0
- storeshot-0.1.0/src/storeshot/_resources/activity.py +30 -0
- storeshot-0.1.0/src/storeshot/_resources/api_keys.py +29 -0
- storeshot-0.1.0/src/storeshot/_resources/auth.py +187 -0
- storeshot-0.1.0/src/storeshot/_resources/billing.py +102 -0
- storeshot-0.1.0/src/storeshot/_resources/catalog.py +43 -0
- storeshot-0.1.0/src/storeshot/_resources/generations.py +88 -0
- storeshot-0.1.0/src/storeshot/_resources/palettes.py +19 -0
- storeshot-0.1.0/src/storeshot/_resources/projects.py +72 -0
- storeshot-0.1.0/src/storeshot/_resources/screenshot_sets.py +160 -0
- storeshot-0.1.0/src/storeshot/_resources/uploads.py +76 -0
- storeshot-0.1.0/src/storeshot/_resources/usage.py +30 -0
- storeshot-0.1.0/src/storeshot/_resources/webhooks.py +101 -0
- storeshot-0.1.0/src/storeshot/_version.py +3 -0
- storeshot-0.1.0/src/storeshot/py.typed +0 -0
- storeshot-0.1.0/src/storeshot/types.py +32 -0
- storeshot-0.1.0/src/storeshot/webhooks.py +95 -0
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0 (unreleased)
|
|
4
|
+
|
|
5
|
+
- First release: a typed, synchronous client for every operation of the Storeshot API contract
|
|
6
|
+
1.17.0, with waits, uploads, archives, pagination, safe retries, idempotency keys, device
|
|
7
|
+
authorization and webhook signature verification.
|
storeshot-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
|
|
2
|
+
Apache License
|
|
3
|
+
Version 2.0, January 2004
|
|
4
|
+
http://www.apache.org/licenses/
|
|
5
|
+
|
|
6
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
7
|
+
|
|
8
|
+
1. Definitions.
|
|
9
|
+
|
|
10
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
11
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
12
|
+
|
|
13
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
14
|
+
the copyright owner that is granting the License.
|
|
15
|
+
|
|
16
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
17
|
+
other entities that control, are controlled by, or are under common
|
|
18
|
+
control with that entity. For the purposes of this definition,
|
|
19
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
20
|
+
direction or management of such entity, whether by contract or
|
|
21
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
22
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
23
|
+
|
|
24
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
25
|
+
exercising permissions granted by this License.
|
|
26
|
+
|
|
27
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
28
|
+
including but not limited to software source code, documentation
|
|
29
|
+
source, and configuration files.
|
|
30
|
+
|
|
31
|
+
"Object" form shall mean any form resulting from mechanical
|
|
32
|
+
transformation or translation of a Source form, including but
|
|
33
|
+
not limited to compiled object code, generated documentation,
|
|
34
|
+
and conversions to other media types.
|
|
35
|
+
|
|
36
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
37
|
+
Object form, made available under the License, as indicated by a
|
|
38
|
+
copyright notice that is included in or attached to the work
|
|
39
|
+
(an example is provided in the Appendix below).
|
|
40
|
+
|
|
41
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
42
|
+
form, that is based on (or derived from) the Work and for which the
|
|
43
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
44
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
45
|
+
of this License, Derivative Works shall not include works that remain
|
|
46
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
47
|
+
the Work and Derivative Works thereof.
|
|
48
|
+
|
|
49
|
+
"Contribution" shall mean any work of authorship, including
|
|
50
|
+
the original version of the Work and any modifications or additions
|
|
51
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
52
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
53
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
54
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
55
|
+
means any form of electronic, verbal, or written communication sent
|
|
56
|
+
to the Licensor or its representatives, including but not limited to
|
|
57
|
+
communication on electronic mailing lists, source code control systems,
|
|
58
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
59
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
60
|
+
excluding communication that is conspicuously marked or otherwise
|
|
61
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
62
|
+
|
|
63
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
64
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
65
|
+
subsequently incorporated within the Work.
|
|
66
|
+
|
|
67
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
68
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
69
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
70
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
71
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
72
|
+
Work and such Derivative Works in Source or Object form.
|
|
73
|
+
|
|
74
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
75
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
76
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
77
|
+
(except as stated in this section) patent license to make, have made,
|
|
78
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
79
|
+
where such license applies only to those patent claims licensable
|
|
80
|
+
by such Contributor that are necessarily infringed by their
|
|
81
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
82
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
83
|
+
institute patent litigation against any entity (including a
|
|
84
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
85
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
86
|
+
or contributory patent infringement, then any patent licenses
|
|
87
|
+
granted to You under this License for that Work shall terminate
|
|
88
|
+
as of the date such litigation is filed.
|
|
89
|
+
|
|
90
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
91
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
92
|
+
modifications, and in Source or Object form, provided that You
|
|
93
|
+
meet the following conditions:
|
|
94
|
+
|
|
95
|
+
(a) You must give any other recipients of the Work or
|
|
96
|
+
Derivative Works a copy of this License; and
|
|
97
|
+
|
|
98
|
+
(b) You must cause any modified files to carry prominent notices
|
|
99
|
+
stating that You changed the files; and
|
|
100
|
+
|
|
101
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
102
|
+
that You distribute, all copyright, patent, trademark, and
|
|
103
|
+
attribution notices from the Source form of the Work,
|
|
104
|
+
excluding those notices that do not pertain to any part of
|
|
105
|
+
the Derivative Works; and
|
|
106
|
+
|
|
107
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
108
|
+
distribution, then any Derivative Works that You distribute must
|
|
109
|
+
include a readable copy of the attribution notices contained
|
|
110
|
+
within such NOTICE file, excluding those notices that do not
|
|
111
|
+
pertain to any part of the Derivative Works, in at least one
|
|
112
|
+
of the following places: within a NOTICE text file distributed
|
|
113
|
+
as part of the Derivative Works; within the Source form or
|
|
114
|
+
documentation, if provided along with the Derivative Works; or,
|
|
115
|
+
within a display generated by the Derivative Works, if and
|
|
116
|
+
wherever such third-party notices normally appear. The contents
|
|
117
|
+
of the NOTICE file are for informational purposes only and
|
|
118
|
+
do not modify the License. You may add Your own attribution
|
|
119
|
+
notices within Derivative Works that You distribute, alongside
|
|
120
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
121
|
+
that such additional attribution notices cannot be construed
|
|
122
|
+
as modifying the License.
|
|
123
|
+
|
|
124
|
+
You may add Your own copyright statement to Your modifications and
|
|
125
|
+
may provide additional or different license terms and conditions
|
|
126
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
127
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
128
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
129
|
+
the conditions stated in this License.
|
|
130
|
+
|
|
131
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
132
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
133
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
134
|
+
this License, without any additional terms or conditions.
|
|
135
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
136
|
+
the terms of any separate license agreement you may have executed
|
|
137
|
+
with Licensor regarding such Contributions.
|
|
138
|
+
|
|
139
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
140
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
141
|
+
except as required for reasonable and customary use in describing the
|
|
142
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
143
|
+
|
|
144
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
145
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
146
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
147
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
148
|
+
implied, including, without limitation, any warranties or conditions
|
|
149
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
150
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
151
|
+
appropriateness of using or redistributing the Work and assume any
|
|
152
|
+
risks associated with Your exercise of permissions under this License.
|
|
153
|
+
|
|
154
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
155
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
156
|
+
unless required by applicable law (such as deliberate and grossly
|
|
157
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
158
|
+
liable to You for damages, including any direct, indirect, special,
|
|
159
|
+
incidental, or consequential damages of any character arising as a
|
|
160
|
+
result of this License or out of the use or inability to use the
|
|
161
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
162
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
163
|
+
other commercial damages or losses), even if such Contributor
|
|
164
|
+
has been advised of the possibility of such damages.
|
|
165
|
+
|
|
166
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
167
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
168
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
169
|
+
or other liability obligations and/or rights consistent with this
|
|
170
|
+
License. However, in accepting such obligations, You may act only
|
|
171
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
172
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
173
|
+
defend, and hold each Contributor harmless for any liability
|
|
174
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
175
|
+
of your accepting any such warranty or additional liability.
|
|
176
|
+
|
|
177
|
+
END OF TERMS AND CONDITIONS
|
|
178
|
+
|
|
179
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
180
|
+
|
|
181
|
+
To apply the Apache License to your work, attach the following
|
|
182
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
183
|
+
replaced with your own identifying information. (Don't include
|
|
184
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
185
|
+
comment syntax for the file format. We also recommend that a
|
|
186
|
+
file or class name and description of purpose be included on the
|
|
187
|
+
same "printed page" as the copyright notice for easier
|
|
188
|
+
identification within third-party archives.
|
|
189
|
+
|
|
190
|
+
Copyright [yyyy] [name of copyright owner]
|
|
191
|
+
|
|
192
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
193
|
+
you may not use this file except in compliance with the License.
|
|
194
|
+
You may obtain a copy of the License at
|
|
195
|
+
|
|
196
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
197
|
+
|
|
198
|
+
Unless required by applicable law or agreed to in writing, software
|
|
199
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
200
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
201
|
+
See the License for the specific language governing permissions and
|
|
202
|
+
limitations under the License.
|
storeshot-0.1.0/NOTICE
ADDED
storeshot-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: storeshot
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Official Python SDK for the Storeshot API: App Store and Google Play marketing screenshots from raw app screenshots.
|
|
5
|
+
Project-URL: Homepage, https://storeshot.art/docs/
|
|
6
|
+
Project-URL: Source, https://github.com/Draggable/storeshot-python
|
|
7
|
+
Project-URL: Changelog, https://github.com/Draggable/storeshot-python/blob/main/CHANGELOG.md
|
|
8
|
+
Author-email: Draggable <support@storeshot.art>
|
|
9
|
+
License-Expression: Apache-2.0
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
License-File: NOTICE
|
|
12
|
+
Keywords: api,app store,google play,screenshots,sdk,storeshot
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.15
|
|
24
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
25
|
+
Classifier: Typing :: Typed
|
|
26
|
+
Requires-Python: >=3.10
|
|
27
|
+
Requires-Dist: httpx<1,>=0.27
|
|
28
|
+
Requires-Dist: typing-extensions>=4.7; python_version < '3.11'
|
|
29
|
+
Description-Content-Type: text/markdown
|
|
30
|
+
|
|
31
|
+
# storeshot
|
|
32
|
+
|
|
33
|
+
The official Python SDK for the [Storeshot API](https://storeshot.art/docs/): turn raw app
|
|
34
|
+
screenshots into App Store and Google Play marketing images. Typed from the API contract (OpenAPI),
|
|
35
|
+
one runtime dependency (httpx), Python 3.10 or later.
|
|
36
|
+
|
|
37
|
+
> This repository is exported from Storeshot's private monorepo, where the SDK is developed and
|
|
38
|
+
> tested; changes land there and arrive here with each release. Issues are welcome here or at
|
|
39
|
+
> support@storeshot.art.
|
|
40
|
+
|
|
41
|
+
## Install
|
|
42
|
+
|
|
43
|
+
```sh
|
|
44
|
+
pip install storeshot
|
|
45
|
+
# or
|
|
46
|
+
uv add storeshot
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## API key
|
|
50
|
+
|
|
51
|
+
The client reads the key from the `STORESHOT_API_KEY` environment variable (or `Storeshot(api_key)`).
|
|
52
|
+
Create one in the developer console (https://console.storeshot.art/):
|
|
53
|
+
|
|
54
|
+
- `ss_test_…` keys are free: everything works, but the images are sample images and no credits
|
|
55
|
+
are used. Start with one.
|
|
56
|
+
- `ss_live_…` keys make real images and spend credits (1 per image).
|
|
57
|
+
|
|
58
|
+
Never print, log or commit a key, and never paste it into a chat. The SDK never logs the key and
|
|
59
|
+
masks it in `repr()`, `str()` and error messages. `Storeshot(environment="test")` refuses any key
|
|
60
|
+
that is not a test key before anything is sent, so a test suite cannot spend credits by accident.
|
|
61
|
+
|
|
62
|
+
No key yet? A tool can get one with its human's approval (device authorization, RFC 8628). A
|
|
63
|
+
client created with `Storeshot(None)` can call only `client.auth.*`; `link_account` shows your
|
|
64
|
+
human a console link and a code through `on_code`, waits for the approval and returns the new key
|
|
65
|
+
once:
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
from storeshot import Storeshot
|
|
69
|
+
|
|
70
|
+
linked = Storeshot(None).auth.link_account(
|
|
71
|
+
client_name="my-tool",
|
|
72
|
+
on_code=lambda code: print(f"Open {code.verification_uri_complete}, confirm {code.user_code}."),
|
|
73
|
+
)
|
|
74
|
+
# Store linked.api_key in your secret store (never print it), then:
|
|
75
|
+
client = Storeshot(linked.api_key)
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
It raises `StoreshotError` `DEVICE_ACCESS_DENIED` or `DEVICE_CODE_EXPIRED` when the human denies
|
|
79
|
+
the request or does not answer within 10 minutes.
|
|
80
|
+
|
|
81
|
+
## Quick start
|
|
82
|
+
|
|
83
|
+
From one raw screenshot (`home.png`) to an App Store listing in fastlane's folder layout:
|
|
84
|
+
|
|
85
|
+
<!-- sdk-readme-example -->
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
from storeshot import Storeshot
|
|
89
|
+
|
|
90
|
+
storeshot = Storeshot() # reads STORESHOT_API_KEY
|
|
91
|
+
upload = storeshot.uploads.create(file="home.png")
|
|
92
|
+
created = storeshot.screenshot_sets.create(
|
|
93
|
+
{
|
|
94
|
+
"app": {"name": "Trailmate"},
|
|
95
|
+
"screens": [{"input": {"uploadId": upload["id"]}, "caption": "Find every trail"}],
|
|
96
|
+
"stores": [{"store": "app_store"}],
|
|
97
|
+
}
|
|
98
|
+
)
|
|
99
|
+
done = storeshot.wait_for_screenshot_set(created)
|
|
100
|
+
print(done["status"], f"{done['counts']['succeeded']}/{done['images']} images")
|
|
101
|
+
storeshot.screenshot_sets.download_archive(done["id"], "listing.zip", layout="fastlane")
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Unzip `listing.zip` in your project root: fastlane `deliver` reads `fastlane/screenshots/`. With a
|
|
105
|
+
live key, ask for the price first: `storeshot.screenshot_sets.quote(body)` returns `images`,
|
|
106
|
+
`credits` and `sufficient` and creates nothing. For a single image use
|
|
107
|
+
`storeshot.generations.create({"inputs": [{"uploadId": upload["id"]}], "store": "app_store",
|
|
108
|
+
"device": "app_store_1290x2796", "app": {"name": "Trailmate"}})`; `storeshot.devices.list()`
|
|
109
|
+
lists the sizes.
|
|
110
|
+
|
|
111
|
+
## Typing
|
|
112
|
+
|
|
113
|
+
Requests and answers are plain dicts whose keys are the API's own names (`uploadId`, `nextCursor`),
|
|
114
|
+
typed as `TypedDict`s from `storeshot.types` (`Generation`, `ScreenshotSet`,
|
|
115
|
+
`CreateScreenshotSetRequest`, …): your editor completes the keys and mypy or pyright check them.
|
|
116
|
+
They match the [API reference](https://storeshot.art/docs/api/) one to one. Only SDK options and
|
|
117
|
+
values the SDK builds itself (`LinkedAccount`, `ResponseMeta`, `DownloadedArchive`) use snake_case.
|
|
118
|
+
|
|
119
|
+
## Waiting
|
|
120
|
+
|
|
121
|
+
Generations and screenshot sets are asynchronous: a create returns `queued` at once.
|
|
122
|
+
|
|
123
|
+
```python
|
|
124
|
+
generation = storeshot.wait_for_generation(created_generation) # or its id
|
|
125
|
+
if generation["status"] == "succeeded":
|
|
126
|
+
print(generation["result"]["image"]["url"])
|
|
127
|
+
|
|
128
|
+
finished = storeshot.wait_for_screenshot_set(set_id, timeout=15 * 60)
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
- `wait_for_generation` long-polls with `Prefer: wait` (the server holds each request up to 60 s).
|
|
132
|
+
- `wait_for_screenshot_set` polls every 3 s (`poll_interval`).
|
|
133
|
+
- Both return the final resource, also when it `failed`, was `canceled` or (sets)
|
|
134
|
+
`partially_failed`: check `status`. They raise `StoreshotTimeoutError` (`WAIT_TIMEOUT`) after
|
|
135
|
+
`timeout` seconds (default 600).
|
|
136
|
+
- Prefer webhooks (below) for long-running work in servers.
|
|
137
|
+
|
|
138
|
+
## Uploads
|
|
139
|
+
|
|
140
|
+
```python
|
|
141
|
+
storeshot.uploads.create(file="screens/home.png") # a path (str or pathlib.Path)
|
|
142
|
+
storeshot.uploads.create(file=data, filename="home.png") # bytes, bytearray or memoryview
|
|
143
|
+
with open("home.png", "rb") as handle:
|
|
144
|
+
storeshot.uploads.create(file=handle) # a binary file object
|
|
145
|
+
storeshot.uploads.create(url="https://example.com/home.png") # the server fetches it
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
PNG, JPEG or WebP, at most 20 MiB (checked before anything is sent) and 50 megapixels. Uploads are
|
|
149
|
+
deleted after 30 days. Reference an upload by `id` in generations and sets of the same key
|
|
150
|
+
environment (test or live).
|
|
151
|
+
|
|
152
|
+
## Downloads
|
|
153
|
+
|
|
154
|
+
```python
|
|
155
|
+
data = storeshot.screenshot_sets.archive(set_id) # bytes
|
|
156
|
+
with storeshot.screenshot_sets.archive_stream(set_id) as stream:
|
|
157
|
+
for chunk in stream.iter_bytes():
|
|
158
|
+
...
|
|
159
|
+
storeshot.screenshot_sets.download_archive(set_id, "listing.zip", layout="fastlane")
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
`download_archive` writes `listing.zip.part` and renames it when complete, so a failed download
|
|
163
|
+
never leaves a partial file. Image URLs in `generation["result"]` are signed for 15 minutes: get
|
|
164
|
+
the generation again for fresh ones.
|
|
165
|
+
|
|
166
|
+
## Pagination
|
|
167
|
+
|
|
168
|
+
```python
|
|
169
|
+
page = storeshot.generations.list({"status": "succeeded", "limit": 50}) # one page
|
|
170
|
+
for generation in storeshot.generations.list_all({"status": "succeeded", "limit": 50}):
|
|
171
|
+
print(generation["id"]) # every page, fetched lazily
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
`list()` returns one page (`data`, `nextCursor`); `list_all()` follows the cursors on demand, so
|
|
175
|
+
`itertools.islice` stops early. Uploads, generations, screenshot sets, projects, activity and
|
|
176
|
+
webhook deliveries (`webhooks.list_all_deliveries(id)`) have it.
|
|
177
|
+
|
|
178
|
+
## Errors
|
|
179
|
+
|
|
180
|
+
Every failure is a `StoreshotError` (subclasses `StoreshotConnectionError`,
|
|
181
|
+
`StoreshotTimeoutError`, `StoreshotWebhookError`):
|
|
182
|
+
|
|
183
|
+
```python
|
|
184
|
+
from storeshot import StoreshotError
|
|
185
|
+
|
|
186
|
+
try:
|
|
187
|
+
storeshot.screenshot_sets.create(body)
|
|
188
|
+
except StoreshotError as error:
|
|
189
|
+
if error.code == "INSUFFICIENT_CREDITS":
|
|
190
|
+
print(f"Not enough credits: top up at {(error.details or {}).get('topUpUrl')}")
|
|
191
|
+
else:
|
|
192
|
+
raise
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
| Attribute | Meaning |
|
|
196
|
+
| --------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
|
|
197
|
+
| `status` | HTTP status (0 when there was no response) |
|
|
198
|
+
| `code` | API error code (`ERROR_CODES`, e.g. `INVALID_INPUT`, `RATE_LIMITED`) or an SDK code (`CONNECTION_ERROR`, `TIMEOUT`, `WAIT_TIMEOUT`, …) |
|
|
199
|
+
| `message` | Human-readable, safe to show |
|
|
200
|
+
| `request_id` | Quote it when asking support@storeshot.art for help |
|
|
201
|
+
| `details` | Code-specific data (`topUpUrl`, `requiredCredits`, `issues`, …) |
|
|
202
|
+
| `doc_url` | Documentation of the code |
|
|
203
|
+
| `retry_after_seconds` | From `Retry-After` (429 and busy 503 answers) |
|
|
204
|
+
|
|
205
|
+
## Retries and timeouts
|
|
206
|
+
|
|
207
|
+
- `timeout` (default 60 s) bounds each network operation of an attempt (connecting, every read,
|
|
208
|
+
every write); archive downloads default to 300 s.
|
|
209
|
+
- `max_retries` (default 2) retries with capped exponential backoff and jitter:
|
|
210
|
+
- `429` and busy `503` answers with `Retry-After`, for every request: the server refused them
|
|
211
|
+
before doing anything. A `Retry-After` longer than `max_retry_after` (default 60 s, e.g. the
|
|
212
|
+
daily generation cap) is not waited for: you get the error with `retry_after_seconds`.
|
|
213
|
+
- Other `5xx`, network errors and timeouts only where repeating is safe: `GET`, creates sent
|
|
214
|
+
with an `Idempotency-Key`, quotes and palettes. Other writes (uploads, webhooks, projects,
|
|
215
|
+
deletes, …) are never repeated blindly; the error says the request may have been processed.
|
|
216
|
+
- Per call: `timeout`, `max_retries`, `request_id`, `headers` and `on_response`
|
|
217
|
+
(`storeshot.me(request_id="build-42")`).
|
|
218
|
+
- `on_response` (client option or per call) receives a `ResponseMeta` with `request_id`,
|
|
219
|
+
`rate_limit`, `rate_limit_policy`, `idempotent_replayed` and the headers of every response.
|
|
220
|
+
|
|
221
|
+
## Idempotency
|
|
222
|
+
|
|
223
|
+
`generations.create`, `screenshot_sets.create`, `billing.create_checkout` and
|
|
224
|
+
`billing.create_subscription_checkout` always send an `Idempotency-Key`: a new UUID per call,
|
|
225
|
+
reused across that call's retries, so a retry never creates or charges twice. To make re-running a
|
|
226
|
+
script safe too, pass your own stable key:
|
|
227
|
+
|
|
228
|
+
```python
|
|
229
|
+
storeshot.screenshot_sets.create(body, idempotency_key="trailmate-release-2.4")
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
The same key with the same body returns the original resource; with a different body it is
|
|
233
|
+
`409 IDEMPOTENCY_CONFLICT`.
|
|
234
|
+
|
|
235
|
+
## Webhooks
|
|
236
|
+
|
|
237
|
+
Register an `https://` endpoint with `storeshot.webhooks.create({"url": url, "events": [...]})`
|
|
238
|
+
(the `whsec_…` secret is returned once), then verify every delivery against the **raw** body:
|
|
239
|
+
|
|
240
|
+
```python
|
|
241
|
+
from flask import Flask, request
|
|
242
|
+
from storeshot import StoreshotWebhookError, construct_webhook_event
|
|
243
|
+
|
|
244
|
+
app = Flask(__name__)
|
|
245
|
+
SECRET = "…" # your whsec_ secret, from your secret store
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
@app.post("/hooks/storeshot")
|
|
249
|
+
def storeshot_hook() -> tuple[str, int]:
|
|
250
|
+
try:
|
|
251
|
+
event = construct_webhook_event(
|
|
252
|
+
request.get_data(), # the raw bytes, not parsed JSON
|
|
253
|
+
request.headers.get("Storeshot-Signature"),
|
|
254
|
+
SECRET,
|
|
255
|
+
)
|
|
256
|
+
except StoreshotWebhookError:
|
|
257
|
+
return "", 400
|
|
258
|
+
# Deduplicate on event["id"], then handle event["type"] / event["data"].
|
|
259
|
+
return "", 204
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
With FastAPI or Starlette, pass `await request.body()`. `verify_webhook_signature(...)` returns a
|
|
263
|
+
`bool` instead of raising. The rules ([webhooks guide](https://storeshot.art/docs/webhooks/)):
|
|
264
|
+
`v1` is HMAC-SHA256 over `<t>.<raw body>`, any `v1` may match (two during a secret rotation),
|
|
265
|
+
compared in constant time; `t` must be within 5 minutes (`tolerance_seconds`). Pass
|
|
266
|
+
`[new_secret, old_secret]` while you roll your own configuration.
|
|
267
|
+
|
|
268
|
+
## Test mode
|
|
269
|
+
|
|
270
|
+
With an `ss_test_…` key everything works like live (validation, uploads, queue, statuses,
|
|
271
|
+
idempotency, archives, webhooks to test webhooks) but no credits are used and no image model runs:
|
|
272
|
+
each result is a sample image of exactly the requested size. Test results are deleted after 30
|
|
273
|
+
days. Test and live are separate: a test key only sees test uploads, generations and webhooks.
|
|
274
|
+
`storeshot.me()["credential"]["environment"]` reports it.
|
|
275
|
+
|
|
276
|
+
## Async frameworks
|
|
277
|
+
|
|
278
|
+
The client is synchronous (an async client is planned). In asyncio code, run calls in a thread:
|
|
279
|
+
|
|
280
|
+
```python
|
|
281
|
+
import asyncio
|
|
282
|
+
|
|
283
|
+
me = await asyncio.to_thread(storeshot.me)
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
## Options
|
|
287
|
+
|
|
288
|
+
- `base_url` (or `STORESHOT_BASE_URL`): `https:`, or `http:` on localhost only. The key is sent
|
|
289
|
+
wherever it points.
|
|
290
|
+
- `http_client`: your own `httpx.Client` (proxies, TLS settings). The SDK never closes it; it must
|
|
291
|
+
not carry its own auth. Redirects are never followed.
|
|
292
|
+
- `default_headers`, `on_response`, and `client_identity={"surface": "cli", "name": "my-tool@1.0"}`
|
|
293
|
+
to label your tool's usage (`X-Storeshot-Client`, default `api/storeshot-sdk-python@<version>`).
|
|
294
|
+
- `with Storeshot() as storeshot:` (or `storeshot.close()`) closes the connection pool.
|
|
295
|
+
- Pyodide/browser pages are refused (`BROWSER_NOT_ALLOWED`): every visitor could read the key.
|
|
296
|
+
`dangerously_allow_browser=True` exists for internal tools only.
|
|
297
|
+
|
|
298
|
+
## Method reference
|
|
299
|
+
|
|
300
|
+
Every API operation has exactly one method; every method takes keyword options last.
|
|
301
|
+
|
|
302
|
+
| Method | Endpoint | Notes |
|
|
303
|
+
| -------------------------------------- | ------------------------------------- | ------------------------ |
|
|
304
|
+
| `activity.list` | `GET /activity` | |
|
|
305
|
+
| `activity.list_all` | `GET /activity` | every page (Python only) |
|
|
306
|
+
| `api_keys.create` | `POST /api-keys` | |
|
|
307
|
+
| `api_keys.list` | `GET /api-keys` | |
|
|
308
|
+
| `api_keys.revoke` | `DELETE /api-keys/{id}` | |
|
|
309
|
+
| `auth.link_account` | `POST /auth/device/token` | whole device flow |
|
|
310
|
+
| `auth.poll_device_token` | `POST /auth/device/token` | no API key needed |
|
|
311
|
+
| `auth.start_device_authorization` | `POST /auth/device` | no API key needed |
|
|
312
|
+
| `billing.cancel_subscription` | `POST /billing/subscription/cancel` | |
|
|
313
|
+
| `billing.change_subscription` | `POST /billing/subscription/change` | |
|
|
314
|
+
| `billing.create_checkout` | `POST /billing/checkout` | auto Idempotency-Key |
|
|
315
|
+
| `billing.create_portal_session` | `POST /billing/portal` | |
|
|
316
|
+
| `billing.create_subscription_checkout` | `POST /billing/subscription/checkout` | auto Idempotency-Key |
|
|
317
|
+
| `billing.get_checkout` | `GET /billing/checkouts/{id}` | |
|
|
318
|
+
| `billing.get_subscription` | `GET /billing/subscription` | |
|
|
319
|
+
| `billing.list_packs` | `GET /billing/packs` | |
|
|
320
|
+
| `billing.list_plans` | `GET /billing/plans` | |
|
|
321
|
+
| `billing.uncancel_subscription` | `POST /billing/subscription/uncancel` | |
|
|
322
|
+
| `credits.get` | `GET /credits` | |
|
|
323
|
+
| `devices.list` | `GET /devices` | |
|
|
324
|
+
| `generations.cancel` | `POST /generations/{id}/cancel` | |
|
|
325
|
+
| `generations.create` | `POST /generations` | auto Idempotency-Key |
|
|
326
|
+
| `generations.delete` | `DELETE /generations/{id}` | |
|
|
327
|
+
| `generations.get` | `GET /generations/{id}` | |
|
|
328
|
+
| `generations.list` | `GET /generations` | |
|
|
329
|
+
| `generations.list_all` | `GET /generations` | every page (Python only) |
|
|
330
|
+
| `health` | `GET /health` | |
|
|
331
|
+
| `me` | `GET /me` | |
|
|
332
|
+
| `palettes.create` | `POST /palettes` | |
|
|
333
|
+
| `projects.add_items` | `POST /projects/{id}/items` | |
|
|
334
|
+
| `projects.create` | `POST /projects` | |
|
|
335
|
+
| `projects.delete` | `DELETE /projects/{id}` | |
|
|
336
|
+
| `projects.get` | `GET /projects/{id}` | |
|
|
337
|
+
| `projects.list` | `GET /projects` | |
|
|
338
|
+
| `projects.list_all` | `GET /projects` | every page (Python only) |
|
|
339
|
+
| `projects.remove_items` | `POST /projects/{id}/items/remove` | |
|
|
340
|
+
| `projects.update` | `PATCH /projects/{id}` | |
|
|
341
|
+
| `screenshot_sets.archive` | `GET /screenshot-sets/{id}/archive` | |
|
|
342
|
+
| `screenshot_sets.archive_stream` | `GET /screenshot-sets/{id}/archive` | |
|
|
343
|
+
| `screenshot_sets.cancel` | `POST /screenshot-sets/{id}/cancel` | |
|
|
344
|
+
| `screenshot_sets.create` | `POST /screenshot-sets` | auto Idempotency-Key |
|
|
345
|
+
| `screenshot_sets.delete` | `DELETE /screenshot-sets/{id}` | |
|
|
346
|
+
| `screenshot_sets.download_archive` | `GET /screenshot-sets/{id}/archive` | |
|
|
347
|
+
| `screenshot_sets.get` | `GET /screenshot-sets/{id}` | |
|
|
348
|
+
| `screenshot_sets.list` | `GET /screenshot-sets` | |
|
|
349
|
+
| `screenshot_sets.list_all` | `GET /screenshot-sets` | every page (Python only) |
|
|
350
|
+
| `screenshot_sets.quote` | `POST /screenshot-sets?dryRun=true` | free, creates nothing |
|
|
351
|
+
| `screenshot_sets.retry` | `POST /screenshot-sets/{id}/retry` | |
|
|
352
|
+
| `status` | `GET /status` | |
|
|
353
|
+
| `stores.list` | `GET /stores` | |
|
|
354
|
+
| `stores.requirements` | `GET /stores/{store}/requirements` | |
|
|
355
|
+
| `uploads.complete` | `POST /uploads/{id}/complete` | |
|
|
356
|
+
| `uploads.create` | `POST /uploads` | |
|
|
357
|
+
| `uploads.delete` | `DELETE /uploads/{id}` | |
|
|
358
|
+
| `uploads.get` | `GET /uploads/{id}` | |
|
|
359
|
+
| `uploads.list` | `GET /uploads` | |
|
|
360
|
+
| `uploads.list_all` | `GET /uploads` | every page (Python only) |
|
|
361
|
+
| `uploads.presign` | `POST /uploads/presign` | |
|
|
362
|
+
| `usage.get` | `GET /usage` | |
|
|
363
|
+
| `wait_for_generation` | `GET /generations/{id}` | |
|
|
364
|
+
| `wait_for_screenshot_set` | `GET /screenshot-sets/{id}` | |
|
|
365
|
+
| `webhooks.create` | `POST /webhooks` | |
|
|
366
|
+
| `webhooks.delete` | `DELETE /webhooks/{id}` | |
|
|
367
|
+
| `webhooks.get` | `GET /webhooks/{id}` | |
|
|
368
|
+
| `webhooks.list` | `GET /webhooks` | |
|
|
369
|
+
| `webhooks.list_all_deliveries` | `GET /webhooks/{id}/deliveries` | every page (Python only) |
|
|
370
|
+
| `webhooks.list_deliveries` | `GET /webhooks/{id}/deliveries` | |
|
|
371
|
+
| `webhooks.rotate_secret` | `POST /webhooks/{id}/rotate-secret` | |
|
|
372
|
+
| `webhooks.test` | `POST /webhooks/{id}/test` | |
|
|
373
|
+
| `webhooks.update` | `PATCH /webhooks/{id}` | |
|
|
374
|
+
|
|
375
|
+
## Versioning
|
|
376
|
+
|
|
377
|
+
`storeshot.__version__` is the package version (0.x until 1.0.0; a 0.x minor may change the
|
|
378
|
+
surface). `storeshot.CONTRACT_VERSION` is the API contract the types were generated from (today
|
|
379
|
+
1.17.0); the API only changes additively within a major version, so a newer API keeps working with
|
|
380
|
+
this SDK. See `CHANGELOG.md`.
|
|
381
|
+
|
|
382
|
+
Licensed under the Apache License 2.0 (`LICENSE`, `NOTICE`).
|