You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
- Retries now match the TS/Py idempotency contract: GET, PUT and
DELETE retry on connection errors and 408/429/500/502/503/504;
POST and PATCH retry only on 429/503, where the server provably did
no work. Previously a POST /shorten answered 500 or 504 was
replayed and could create duplicate links.
- ExportLink hits /api/v1/export/links/{id}: only the per-link route
names the download after the link, so aggregate exports of
different links no longer overwrite each other on disk. Aggregate
slicing stays reachable via Export with a url_id filter.
- Error.Code docs and test fixtures now show the backend's real
lowercase snake_case codes (conflict, not_found, blocked, ...) with
the one uppercase outlier EMAIL_NOT_VERIFIED named.
- ListURLsOptions covers the full filter object: CreatedAfter,
CreatedBefore, and tri-state PasswordSet / MaxClicksSet via
Opt[bool].
- LinkStats and ExportLink reject the aggregate-only short_code and
url_id filters client-side instead of letting the endpoint 422.
- 451 affordance: IsBlocked predicate plus the ErrLinkBlocked
sentinel, attached in newError, for the live safety takedown.
Copy file name to clipboardExpand all lines: README.md
+9-6Lines changed: 9 additions & 6 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -169,7 +169,7 @@ metadata. Retrieve it with `errors.As`:
169
169
| What you get | Where |
170
170
| --- | --- |
171
171
| HTTP status |`err.StatusCode`|
172
-
| Machine-readable code |`err.Code` (open string enum, e.g. `CONFLICT_ERROR`) |
172
+
| Machine-readable code |`err.Code` (lowercase snake_case, e.g. `conflict`, `not_found`, `blocked`; the one uppercase outlier is `EMAIL_NOT_VERIFIED`) |
173
173
| Human-readable message |`err.Message`, plus `err.Field` on validation errors |
174
174
| Request id for support |`err.RequestID`|
175
175
| Rate-limit state |`err.RateLimit` (limit, remaining, reset, retry-after) |
@@ -182,13 +182,16 @@ Common branches have predicates and sentinels:
182
182
|`spoo.IsRateLimited(err)`| 429: budget exhausted even after retries |
183
183
|`errors.Is(err, spoo.ErrSessionExpired)`| the refresh token no longer works; log in again |
184
184
|`errors.Is(err, spoo.ErrLinkPasswordProtected)`| the link's stats need the link password |
185
+
|`spoo.IsBlocked(err)`| 451: the link was taken down by the safety pipeline |
185
186
186
187
## Retries
187
188
188
-
Connection errors, 408, 429, and 5xx responses are retried twice by default
189
-
with exponential backoff and jitter. A `Retry-After` header is authoritative
190
-
when the server sends one. Configure with `option.WithMaxRetries(n)`; 0
191
-
disables retries.
189
+
Idempotent requests (GET, PUT, DELETE) are retried twice by default on
190
+
connection errors and 408, 429, 500, 502, 503 and 504 responses, with
191
+
exponential backoff and jitter. Requests that are not idempotent are only
192
+
retried when the server provably did no work (429 and 503). A `Retry-After`
193
+
header is authoritative when the server sends one. Configure with
194
+
`option.WithMaxRetries(n)`; 0 disables retries.
192
195
193
196
## Pagination
194
197
@@ -247,7 +250,7 @@ file, or database, and rotated tokens persist through it.
0 commit comments