@omelhorsite/sdk 0.1.0

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 (36) hide show
  1. package/README.md +321 -0
  2. package/dist/index.js +11589 -0
  3. package/dist/types/auth/device.d.ts +156 -0
  4. package/dist/types/auth/index.d.ts +127 -0
  5. package/dist/types/auth/tokens.d.ts +356 -0
  6. package/dist/types/client.d.ts +133 -0
  7. package/dist/types/errors.d.ts +202 -0
  8. package/dist/types/http.d.ts +204 -0
  9. package/dist/types/index.d.ts +33 -0
  10. package/dist/types/local/index.d.ts +42 -0
  11. package/dist/types/local/password.d.ts +169 -0
  12. package/dist/types/local/qr.d.ts +127 -0
  13. package/dist/types/local/wordlist.d.ts +26 -0
  14. package/dist/types/resources/account.d.ts +296 -0
  15. package/dist/types/resources/chests.d.ts +194 -0
  16. package/dist/types/resources/dynamicQrs.d.ts +172 -0
  17. package/dist/types/resources/forms.d.ts +331 -0
  18. package/dist/types/resources/index.d.ts +30 -0
  19. package/dist/types/resources/ipLookup.d.ts +63 -0
  20. package/dist/types/resources/jobs.d.ts +233 -0
  21. package/dist/types/resources/linkTrees.d.ts +249 -0
  22. package/dist/types/resources/notepads.d.ts +96 -0
  23. package/dist/types/resources/shortLinks.d.ts +248 -0
  24. package/dist/types/resources/storage/upload.d.ts +459 -0
  25. package/dist/types/resources/storage.d.ts +527 -0
  26. package/dist/types/resources/tickets.d.ts +236 -0
  27. package/dist/types/resources/tools/backgroundRemoval.d.ts +99 -0
  28. package/dist/types/resources/tools/captions.d.ts +318 -0
  29. package/dist/types/resources/tools/downloader.d.ts +397 -0
  30. package/dist/types/resources/tools/index.d.ts +215 -0
  31. package/dist/types/resources/tools/jumpstyle.d.ts +194 -0
  32. package/dist/types/resources/tools/transcription.d.ts +178 -0
  33. package/dist/types/resources/tools/upscale.d.ts +94 -0
  34. package/dist/types/resources/tools/vocalSeparation.d.ts +183 -0
  35. package/dist/types/types.d.ts +245 -0
  36. package/package.json +37 -0
@@ -0,0 +1,249 @@
1
+ /**
2
+ * The `linkTrees` namespace: link-in-bio pages, their click statistics and the
3
+ * optional CV attached to one.
4
+ *
5
+ * A tree is public at `by_slug/:slug`, and the slug is the identity a visitor
6
+ * sees. Owner operations go through the record id, exactly like every other
7
+ * resource. The slug is also the endpoint of a paired short link, so changing
8
+ * it renames that link inside the same transaction and the old URL stops
9
+ * resolving.
10
+ */
11
+ import { Resource } from "../http";
12
+ import type { BaseRecord, FileInput, Id, RequestOptions, Timestamp } from "../types";
13
+ /** Longest a slug may be. The shortest is 2. */
14
+ export declare const LINK_TREE_SLUG_MAX_LENGTH = 63;
15
+ /** Most items one tree may carry. */
16
+ export declare const LINK_TREE_MAX_ITEMS = 30;
17
+ /** Ceiling on the attached CV, which must also be a PDF. */
18
+ export declare const LINK_TREE_CV_MAX_BYTES: number;
19
+ /**
20
+ * One entry on a link tree.
21
+ *
22
+ * `url` must carry an `http`, `https`, `mailto`, `tel` or `sms` scheme; a bare
23
+ * domain is rejected by the model, not silently fixed.
24
+ */
25
+ export interface LinkTreeItem {
26
+ /** Stable within the tree, and the key click tracking is filed under. The
27
+ * server mints a UUID when you leave it out, so read the tree back before
28
+ * calling {@link LinkTreesNamespace.trackClick}. */
29
+ readonly id?: string;
30
+ /** Up to 80 characters. */
31
+ readonly label: string;
32
+ /** Up to 1024 characters, with a scheme. */
33
+ readonly url: string;
34
+ /** Icon slug the renderer resolves. Up to 40 characters. */
35
+ readonly icon?: string;
36
+ /** Up to 200 characters. */
37
+ readonly description?: string;
38
+ /** `data:image/...` under 80 KB, for a custom icon. */
39
+ readonly icon_image?: string;
40
+ }
41
+ /**
42
+ * Styling of a link tree. Colours must be `#RRGGBB` and images must be
43
+ * `data:image/` URIs; anything else in here is dropped in silence on write.
44
+ */
45
+ export interface LinkTreeTheme {
46
+ readonly bg_color?: string;
47
+ readonly text_color?: string;
48
+ readonly button_bg?: string;
49
+ readonly button_text?: string;
50
+ readonly button_border_color?: string;
51
+ readonly button_style?: "solid" | "outline" | "rounded" | "square";
52
+ /** Clamped to 0..8. */
53
+ readonly button_border_width?: number;
54
+ /** `data:image/...` under 1.5 MB, or `null` to clear. */
55
+ readonly bg_image?: string | null;
56
+ /** `data:image/...` under 1 MB, or `null` to clear. */
57
+ readonly banner_image?: string | null;
58
+ }
59
+ /** A link-in-bio page, owner view. */
60
+ export interface LinkTree extends BaseRecord {
61
+ readonly user_id: Id;
62
+ /** Public path segment. Lowercase letters, digits and dashes. */
63
+ readonly slug: string;
64
+ readonly title: string;
65
+ readonly bio?: string | null;
66
+ /** The avatar, inline as a `data:image/` URI rather than a URL. */
67
+ readonly avatar_data_url?: string | null;
68
+ readonly items: LinkTreeItem[];
69
+ readonly theme: LinkTreeTheme;
70
+ /** Click counters keyed by {@link LinkTreeItem.id}. */
71
+ readonly clicks_by_item: Record<string, number>;
72
+ /** Absolute URL that downloads the CV, or `null` when none is attached. */
73
+ readonly cv_url: string | null;
74
+ readonly cv_filename: string | null;
75
+ /** The shareable short URL. */
76
+ readonly public_url: string;
77
+ /** Endpoint of the paired short link. Always equal to `slug`. */
78
+ readonly short_link_endpoint: string;
79
+ readonly short_link_namespace: string;
80
+ }
81
+ /**
82
+ * The visitor's view: the same page with the owner-only fields removed. Note
83
+ * that it carries no timestamps, so it is NOT a {@link LinkTree}.
84
+ */
85
+ export interface PublicLinkTree {
86
+ readonly id: Id;
87
+ readonly slug: string;
88
+ readonly title: string;
89
+ readonly bio?: string | null;
90
+ readonly avatar_data_url?: string | null;
91
+ readonly items: LinkTreeItem[];
92
+ readonly theme: LinkTreeTheme;
93
+ readonly cv_url: string | null;
94
+ readonly cv_filename: string | null;
95
+ }
96
+ /** Arguments for creating a link tree. */
97
+ export interface CreateLinkTreeInput {
98
+ /**
99
+ * 2 to {@link LINK_TREE_SLUG_MAX_LENGTH} characters of `[a-z0-9-]`, starting
100
+ * and ending alphanumeric, and never one of `new edit admin api root login
101
+ * logout signup signin manage`.
102
+ */
103
+ readonly slug: string;
104
+ /** Required: the model refuses a blank title. Up to 80 characters. */
105
+ readonly title: string;
106
+ /** Up to 280 characters. */
107
+ readonly bio?: string;
108
+ /** `data:image/...` under 500 KB. Build one with `dataUrlFromFile`. */
109
+ readonly avatarDataUrl?: string;
110
+ readonly items?: LinkTreeItem[];
111
+ readonly theme?: LinkTreeTheme;
112
+ }
113
+ /**
114
+ * Fields that can change afterwards. A true PATCH: only the keys you pass are
115
+ * touched.
116
+ *
117
+ * `items` REPLACES the whole list - there is no per-item endpoint, so read,
118
+ * edit the array, write it back, and accept that two concurrent editors
119
+ * clobber each other. Keep each item's `id` when you do, or the click counters
120
+ * in `clicks_by_item` stop lining up with anything.
121
+ */
122
+ export interface UpdateLinkTreeInput {
123
+ /** Renames the paired short link; the old public URL stops resolving. */
124
+ readonly slug?: string;
125
+ readonly title?: string;
126
+ readonly bio?: string;
127
+ readonly avatarDataUrl?: string | null;
128
+ readonly items?: LinkTreeItem[];
129
+ readonly theme?: LinkTreeTheme;
130
+ }
131
+ /** One day of the click histogram. Always 30 entries, oldest first. */
132
+ export interface LinkTreeDailyStat {
133
+ /** `YYYY-MM-DD`. */
134
+ readonly date: string;
135
+ readonly count: number;
136
+ }
137
+ /** One row of {@link LinkTreeStats.top_countries}. */
138
+ export interface LinkTreeCountryStat {
139
+ readonly country: string;
140
+ readonly count: number;
141
+ }
142
+ /** One row of {@link LinkTreeStats.top_devices}. */
143
+ export interface LinkTreeDeviceStat {
144
+ readonly device_name: string;
145
+ readonly count: number;
146
+ }
147
+ /**
148
+ * `GET /link_trees/:id/stats`.
149
+ *
150
+ * The totals and the histogram come from the paired short link, so they count
151
+ * visits to the PAGE. `clicks_by_item` comes from the tree itself and counts
152
+ * clicks on the links, so the two are not comparable and will not add up.
153
+ */
154
+ export interface LinkTreeStats {
155
+ readonly total_clicks: number;
156
+ readonly last_click_at: Timestamp | null;
157
+ /** The last 30 days, one bucket per day. */
158
+ readonly clicks_daily: LinkTreeDailyStat[];
159
+ /** Up to ten, biggest first. */
160
+ readonly top_countries: LinkTreeCountryStat[];
161
+ /** Up to five, biggest first. */
162
+ readonly top_devices: LinkTreeDeviceStat[];
163
+ /** Clicks keyed by {@link LinkTreeItem.id}. */
164
+ readonly clicks_by_item: Record<string, number>;
165
+ }
166
+ /**
167
+ * `GET /link_trees/slug_availability`. Read `available`; `suggestions` is only
168
+ * present when the slug is well formed but taken.
169
+ */
170
+ export interface LinkTreeSlugAvailability {
171
+ readonly slug: string;
172
+ readonly valid: boolean;
173
+ readonly available: boolean;
174
+ /** `"invalid"` for the format, `"reserved"` for the blocklist. */
175
+ readonly reason?: string;
176
+ readonly suggestions?: string[];
177
+ }
178
+ /** The `linkTrees` namespace, reachable as `oms.linkTrees`. */
179
+ export declare class LinkTreesNamespace extends Resource {
180
+ /**
181
+ * `GET /link_trees` - the pages you own, most recently edited first.
182
+ *
183
+ * Not paginated and not filterable: the endpoint takes no modifiers and
184
+ * answers with the whole set.
185
+ */
186
+ list(options?: RequestOptions): Promise<LinkTree[]>;
187
+ /** `GET /link_trees/:id` - owner view, with the click counters. */
188
+ get(id: Id, options?: RequestOptions): Promise<LinkTree>;
189
+ /**
190
+ * `GET /link_trees/by_slug/:slug` - the visitor's view. No credential
191
+ * needed, and unlike the forms equivalent it counts nothing.
192
+ */
193
+ getPublic(slug: string, options?: RequestOptions): Promise<PublicLinkTree>;
194
+ /**
195
+ * `GET /link_trees/slug_availability` - whether a slug is free, with
196
+ * alternatives when it is not. The server is still the authority: create can
197
+ * lose a race and answer 400.
198
+ */
199
+ slugAvailability(slug: string, options?: RequestOptions): Promise<LinkTreeSlugAvailability>;
200
+ /** {@link slugAvailability} reduced to its verdict. */
201
+ slugAvailable(slug: string, options?: RequestOptions): Promise<boolean>;
202
+ /**
203
+ * `POST /link_trees` - creates the page and reserves its slug in one
204
+ * transaction.
205
+ *
206
+ * Not retried by default: a replayed create would fail on the slug being
207
+ * taken by its own first attempt.
208
+ */
209
+ create(input: CreateLinkTreeInput, options?: RequestOptions): Promise<LinkTree>;
210
+ /**
211
+ * `PATCH /link_trees/:id`. Only the keys you pass are touched. See the note
212
+ * on {@link UpdateLinkTreeInput.items}: the list is replaced wholesale.
213
+ */
214
+ update(id: Id, input: UpdateLinkTreeInput, options?: RequestOptions): Promise<LinkTree>;
215
+ /** `DELETE /link_trees/:id`. Takes the paired short link with it. */
216
+ delete(id: Id, options?: RequestOptions): Promise<void>;
217
+ /** `GET /link_trees/:id/stats` - page visits, and the per-item breakdown. */
218
+ stats(id: Id, options?: RequestOptions): Promise<LinkTreeStats>;
219
+ /**
220
+ * `PATCH /link_trees/:id/upload_cv` - attaches a PDF visitors can download.
221
+ *
222
+ * Multipart, and PDF only: another content type or anything over
223
+ * {@link LINK_TREE_CV_MAX_BYTES} is rejected with 400. Replaces whatever was
224
+ * attached before.
225
+ */
226
+ uploadCv(id: Id, file: FileInput, options?: RequestOptions): Promise<LinkTree>;
227
+ /** `DELETE /link_trees/:id/remove_cv` - returns the updated tree, not 204. */
228
+ removeCv(id: Id, options?: RequestOptions): Promise<LinkTree>;
229
+ /**
230
+ * `GET /link_trees/by_slug/:slug/cv` - downloads the attached CV.
231
+ *
232
+ * Served inline by Rails rather than redirected, so the bytes come through
233
+ * the API. The filename is on the record, as `cv_filename`.
234
+ */
235
+ downloadCv(slug: string, options?: RequestOptions): Promise<Blob>;
236
+ /**
237
+ * `POST /link_trees/by_slug/:slug/track_click` - records a click on one
238
+ * item.
239
+ *
240
+ * Only a client rendering the page itself should call this. The SDK's own
241
+ * read methods never track, so reading a tree does not pollute its owner's
242
+ * statistics.
243
+ *
244
+ * Answers 204 whatever happens - an unknown slug and an unknown item id both
245
+ * look like success, deliberately, so the endpoint cannot be used to probe
246
+ * which slugs exist.
247
+ */
248
+ trackClick(slug: string, itemId: string, options?: RequestOptions): Promise<void>;
249
+ }
@@ -0,0 +1,96 @@
1
+ /**
2
+ * The `notepads` namespace: anonymous shared scratch pads.
3
+ *
4
+ * A pad is addressed by its slug and by nothing else. `Notepad.viewable_by`
5
+ * returns `none` by construction and the blueprint strips the numeric `id` from
6
+ * every view, precisely so that nobody can walk primary keys into other
7
+ * people's pads. Knowing the slug IS the whole authorisation story: anyone
8
+ * holding it can read AND rewrite the pad.
9
+ *
10
+ * So treat a slug as a bearer secret. Do not log it, do not put it in a URL you
11
+ * paste somewhere, and do not put anything in a pad you would mind a stranger
12
+ * reading - the slugs the server mints come from `Faker::Internet.unique.slug`,
13
+ * which is word-shaped and short.
14
+ *
15
+ * Pads that are still empty an hour after their last write are swept away by a
16
+ * background job.
17
+ */
18
+ import { Resource } from "../http";
19
+ import type { RequestOptions, Timestamp } from "../types";
20
+ /**
21
+ * A shared notepad.
22
+ *
23
+ * There is no `id`: the blueprint excludes it from the default view AND from
24
+ * `:extended`, so this is one of the very few records in the API that does not
25
+ * extend `BaseRecord`. The slug is the identifier.
26
+ */
27
+ export interface Notepad {
28
+ /** Word-shaped capability, e.g. `"quick-brown-fox"`. Treat it as a secret. */
29
+ readonly slug: string;
30
+ /** The whole document. Empty string, never `null`, for a fresh pad. */
31
+ readonly content: string;
32
+ readonly created_at: Timestamp;
33
+ readonly updated_at: Timestamp;
34
+ /**
35
+ * Endpoint of the `n/` short link the backend keeps pointing at this pad,
36
+ * so `https://omelhor.site/n/<endpoint>` opens it in the web tool.
37
+ *
38
+ * Only {@link NotepadsNamespace.showOrCreate} sends this field;
39
+ * {@link NotepadsNamespace.update} answers with the bare record.
40
+ */
41
+ readonly short_link_endpoint?: string;
42
+ }
43
+ /** Arguments for {@link NotepadsNamespace.showOrCreate}. */
44
+ export interface OpenNotepadInput {
45
+ /**
46
+ * Slug to open. Omit it to mint a brand new pad - that branch is throttled
47
+ * per IP, so never omit it in a loop and never omit it just to poll.
48
+ */
49
+ readonly slug?: string;
50
+ }
51
+ /** The `notepads` namespace, reachable as `oms.notepads`. */
52
+ export declare class NotepadsNamespace extends Resource {
53
+ /**
54
+ * `GET /notepads/show_or_create` - opens the pad with this slug, creating it
55
+ * if no pad has that slug yet.
56
+ *
57
+ * Fully anonymous: no credential is sent, none is needed, and one would not
58
+ * change the answer.
59
+ *
60
+ * Two very different calls share this one route, and they have different
61
+ * costs:
62
+ *
63
+ * - **with a slug** it is a plain read, cheap and unthrottled beyond the
64
+ * general ceiling. This is the call a poller makes;
65
+ * - **without a slug** it MINTS a row, and minting is capped at **20 pads
66
+ * per hour per IP** by a counter inside the controller (deliberately not in
67
+ * rack-attack, so that polling reads do not spend the budget). Over the cap
68
+ * the answer is `429` with the body `{"error":"rate_limited"}` and **no**
69
+ * `Retry-After` header, which arrives here as an {@link OmsQuotaError}
70
+ * whose `retryAfterMs` is `undefined`. Waiting is the only cure; there is
71
+ * nothing to read off the response.
72
+ *
73
+ * Retries are disabled for the mint branch: every attempt would generate a
74
+ * DIFFERENT random slug, so a retry after a lost response leaves an orphan
75
+ * pad behind and hands you the wrong one. The read branch keeps the client's
76
+ * normal retry policy.
77
+ *
78
+ * @throws {OmsQuotaError} 429 when the per-IP creation budget is spent.
79
+ */
80
+ showOrCreate(input?: OpenNotepadInput, options?: RequestOptions): Promise<Notepad>;
81
+ /**
82
+ * `PATCH /notepads/:slug` - replaces the whole content.
83
+ *
84
+ * The `:id` path segment is the SLUG, not a database id; the controller looks
85
+ * the pad up by slug and then requires that same slug back as proof the
86
+ * caller was handed the pad rather than guessing at it.
87
+ *
88
+ * There is no diff and no merge: the last write wins, and two editors will
89
+ * clobber each other silently. A host that wants collaborative editing has to
90
+ * reconcile before calling. `content` is the only writable field.
91
+ *
92
+ * @throws {OmsApiError} 404 when no pad has that slug. Note the asymmetry
93
+ * with {@link showOrCreate}, which would have created it.
94
+ */
95
+ update(slug: string, content: string, options?: RequestOptions): Promise<Notepad>;
96
+ }
@@ -0,0 +1,248 @@
1
+ /**
2
+ * The `shortLinks` namespace: URL shortening plus per-link click statistics.
3
+ *
4
+ * This module is the reference implementation of the resource pattern. Every
5
+ * other resource in the SDK is shaped exactly like it, and CONTRACT.md quotes
6
+ * it verbatim. Change the pattern here and change it everywhere, or do not
7
+ * change it at all.
8
+ *
9
+ * Creating a link works anonymously; listing, editing and statistics need a
10
+ * credential. Creation is the most tightly throttled write in the whole API -
11
+ * see {@link ShortLinksNamespace.create} before you spend one.
12
+ */
13
+ import { Resource } from "../http";
14
+ import { type BaseRecord, type Id, type JsonObject, type PageParams, type Paginated, type RequestOptions, type Timestamp } from "../types";
15
+ /** Public host that fronts `short_links#follow`. See {@link ShortLinksNamespace.shortUrl}. */
16
+ export declare const SHORT_LINK_BASE_URL = "https://omelhor.site";
17
+ /**
18
+ * Primary key of a short link.
19
+ *
20
+ * Short links predate the string ids the rest of the API uses: the table still
21
+ * has an integer primary key, so the JSON carries a **number** here while the
22
+ * `user_id` right next to it is a string. Every method accepts either form and
23
+ * interpolates it into the path, so a caller never has to care.
24
+ */
25
+ export type ShortLinkId = Id | number;
26
+ /** One recorded visit to a short link. */
27
+ export interface ShortLinkClick {
28
+ readonly id: number;
29
+ readonly created_at: Timestamp;
30
+ readonly updated_at: Timestamp;
31
+ /** ISO 3166-1 alpha-2 resolved from the visitor's IP, or `null`. */
32
+ readonly country: string | null;
33
+ /** Browser or app name parsed from the user agent, or `null`. */
34
+ readonly device_name: string | null;
35
+ }
36
+ /**
37
+ * A short link.
38
+ *
39
+ * `Omit<BaseRecord, "id">` rather than a plain `extends BaseRecord`: see
40
+ * {@link ShortLinkId} for why the identifier is a number on this one table.
41
+ */
42
+ export interface ShortLink extends Omit<BaseRecord, "id"> {
43
+ /** Integer primary key. See {@link ShortLinkId}. */
44
+ readonly id: number;
45
+ /** The destination the link redirects to. */
46
+ readonly url: string;
47
+ /** The path segment after the domain, e.g. `"my-talk"`. */
48
+ readonly endpoint: string;
49
+ /**
50
+ * Namespace the endpoint lives under. User links have `null` or `""`; the
51
+ * internal tools reserve `"n"` (notepads), `"c"` (chests), `"ss"` (storage
52
+ * shares), `"qr"` (dynamic QR), `"f"` (forms) and `"t"` (link trees). That
53
+ * is why {@link ShortLink} and `DynamicQr` are separate resources even
54
+ * though both are rows in the same table.
55
+ */
56
+ readonly namespace: string | null;
57
+ /** Owner, or `null` for a link created anonymously. */
58
+ readonly user_id: Id | null;
59
+ /**
60
+ * EVERY click ever recorded, inlined by the blueprint - not a count, not a
61
+ * page. A link with 50 000 visits sends 50 000 objects here. Use
62
+ * {@link ShortLinksNamespace.stats} for anything analytical and treat this
63
+ * field as a payload hazard, not as a feature.
64
+ */
65
+ readonly short_link_clicks?: ShortLinkClick[];
66
+ /** The owner rendered inline (the same shape `oms.account` returns), or `null`. */
67
+ readonly user?: JsonObject | null;
68
+ }
69
+ /** One day of the click histogram. */
70
+ export interface ShortLinkDailyClicks {
71
+ /** `YYYY-MM-DD`. */
72
+ readonly date: string;
73
+ readonly count: number;
74
+ }
75
+ /** Clicks grouped by the visitor's country. */
76
+ export interface ShortLinkCountryClicks {
77
+ /** ISO 3166-1 alpha-2 as it was stored at click time. */
78
+ readonly country: string;
79
+ readonly count: number;
80
+ }
81
+ /** Clicks grouped by the visitor's browser or app. */
82
+ export interface ShortLinkDeviceClicks {
83
+ readonly device_name: string;
84
+ readonly count: number;
85
+ }
86
+ /**
87
+ * `GET /short_links/:id/stats`, and the identical body
88
+ * `GET /dynamic_qrs/:id/stats` returns.
89
+ *
90
+ * Five keys, always all five. There is no referrer breakdown and no unique
91
+ * visitor count: the backend stores neither.
92
+ */
93
+ export interface ShortLinkStats {
94
+ readonly total_clicks: number;
95
+ /** Timestamp of the most recent click, or `null` when there has never been one. */
96
+ readonly last_click_at: Timestamp | null;
97
+ /**
98
+ * Exactly 30 entries, oldest first, ending today. Days with no clicks are
99
+ * present with `count: 0`, so the series is safe to plot without gap
100
+ * filling. The window is FIXED server-side; there is no way to ask for
101
+ * another one.
102
+ */
103
+ readonly clicks_daily: ShortLinkDailyClicks[];
104
+ /** Top 10 countries by clicks, descending. Rows with no country are excluded. */
105
+ readonly top_countries: ShortLinkCountryClicks[];
106
+ /** Top 5 devices by clicks, descending. Rows with no device are excluded. */
107
+ readonly top_devices: ShortLinkDeviceClicks[];
108
+ }
109
+ /** Arguments for creating a short link. */
110
+ export interface CreateShortLinkInput {
111
+ /**
112
+ * Absolute destination URL. Must be `http` or `https` and must parse against
113
+ * `URI::DEFAULT_PARSER`; anything else is a 400, not a silent rewrite.
114
+ */
115
+ readonly url: string;
116
+ /**
117
+ * Requested endpoint. Omit it and the server mints a random alphanumeric one
118
+ * (4 characters, growing until it finds a free one). A taken endpoint comes
119
+ * back as a 400, never as a silent rename.
120
+ *
121
+ * Anonymous callers may pick an endpoint too - the server only generates one
122
+ * when the field is blank.
123
+ */
124
+ readonly endpoint?: string;
125
+ }
126
+ /** Fields that can change after a link exists. */
127
+ export interface UpdateShortLinkInput {
128
+ readonly url?: string;
129
+ readonly endpoint?: string;
130
+ }
131
+ /** Filters for {@link ShortLinksNamespace.list}. */
132
+ export interface ListShortLinksParams extends PageParams {
133
+ /**
134
+ * Narrow to one owner, sent as `exact_search[user_id]`.
135
+ *
136
+ * The listing is already scoped to the caller server-side, so this can only
137
+ * ever narrow your own links to yourself or to nothing. It exists because
138
+ * the web app sends it; there is no admin escape hatch here.
139
+ */
140
+ readonly userId?: Id;
141
+ }
142
+ /** The `shortLinks` namespace, reachable as `oms.shortLinks`. */
143
+ export declare class ShortLinksNamespace extends Resource {
144
+ /**
145
+ * `GET /short_links` - the links you own, newest first.
146
+ *
147
+ * Only user-managed links are listed: anything in a system namespace
148
+ * (notepads, chests, dynamic QR, forms, link trees) is filtered out server
149
+ * side, so a dynamic QR will never show up here. Use `oms.dynamicQrs.list()`
150
+ * for those.
151
+ *
152
+ * The endpoint has NO default ordering of its own, which would make paging
153
+ * non-deterministic, so the SDK always sends one (`created_at:desc` unless
154
+ * you override `order`).
155
+ *
156
+ * Beware {@link ShortLink.short_link_clicks}: every row carries its full
157
+ * click history. A page of 500 busy links is a very large response.
158
+ *
159
+ * @throws {OmsAuthError} 401 when anonymous.
160
+ */
161
+ list(params?: ListShortLinksParams, options?: RequestOptions): Promise<Paginated<ShortLink>>;
162
+ /**
163
+ * `POST /short_links` - shortens a URL.
164
+ *
165
+ * **Rate limit, read this before you spend one:** rack-attack allows
166
+ * **10 creations per hour per IP** (`short_links_create/ip`), and the rule
167
+ * is keyed by IP for EVERYONE - being signed in does not buy you a bigger
168
+ * budget. The 11th call in an hour answers `429` with a `Retry-After` header
169
+ * measured in whatever is left of that hour, which arrives here as an
170
+ * {@link OmsQuotaError} with `retryAfterMs` set. An agent that shortens URLs
171
+ * in a loop will burn the whole hour's budget in about a second, so batch the
172
+ * decision, not the calls.
173
+ *
174
+ * Retries are disabled by default for exactly two reasons: a replayed `POST`
175
+ * after a 502 mints a SECOND link, and the transport's 429 handling honours
176
+ * `Retry-After` literally, which here means sleeping for up to an hour inside
177
+ * one call. Pass `retry` explicitly if you want either behaviour back.
178
+ *
179
+ * Works anonymously, in which case the link has no owner and cannot be
180
+ * listed, edited or measured afterwards - the endpoint string is the only
181
+ * handle you will ever have on it. Capture the response.
182
+ *
183
+ * @throws {OmsQuotaError} 429 once the hourly budget is spent.
184
+ * @throws {OmsApiError} 400 when `endpoint` is taken or `url` is not an
185
+ * `http`/`https` URL. The body is a plain sentence, so read `message`.
186
+ */
187
+ create(input: CreateShortLinkInput, options?: RequestOptions): Promise<ShortLink>;
188
+ /**
189
+ * `PATCH /short_links/:id` - repoints a link or renames its endpoint.
190
+ *
191
+ * Renaming frees the old endpoint immediately, and every printed or shared
192
+ * copy of it stops working. There is no redirect from the old name.
193
+ *
194
+ * @throws {OmsAuthError} 401 when anonymous.
195
+ * @throws {OmsApiError} 404 when the link is not yours, 400 on a taken
196
+ * endpoint or an invalid URL.
197
+ */
198
+ update(id: ShortLinkId, input: UpdateShortLinkInput, options?: RequestOptions): Promise<ShortLink>;
199
+ /**
200
+ * `DELETE /short_links/:id`. The endpoint becomes free again immediately and
201
+ * the recorded clicks go with it.
202
+ *
203
+ * @throws {OmsApiError} 404 when the link is not yours.
204
+ */
205
+ delete(id: ShortLinkId, options?: RequestOptions): Promise<void>;
206
+ /**
207
+ * `GET /short_links/:id/stats` - totals plus a fixed 30-day daily histogram.
208
+ *
209
+ * The window is not configurable: the backend always buckets the last 30
210
+ * days. For anything else, read {@link ShortLink.short_link_clicks} off the
211
+ * record and bucket it yourself - at the cost noted on that field.
212
+ *
213
+ * @throws {OmsAuthError} 401 when anonymous (checked before the lookup, so an
214
+ * anonymous caller cannot probe which ids exist).
215
+ * @throws {OmsApiError} 404 when the link is not yours.
216
+ */
217
+ stats(id: ShortLinkId, options?: RequestOptions): Promise<ShortLinkStats>;
218
+ /**
219
+ * Finds one of YOUR links by its endpoint, without counting a click.
220
+ *
221
+ * The public `follow` route records a visit before it redirects, so using it
222
+ * to read a destination would quietly corrupt the owner's statistics. The
223
+ * API offers no read-by-endpoint, and `search[endpoint]` is not on the
224
+ * allowlist (an unknown filter key is a 400, not a wider result), so the only
225
+ * honest implementation is to page through your own listing and match
226
+ * locally. That has three consequences worth knowing:
227
+ *
228
+ * - it needs a credential, and it only ever finds links you own;
229
+ * - it cannot see a system-namespace endpoint (`qr/...`, `n/...`), because
230
+ * the listing filters those out;
231
+ * - it costs one request per {@link RESOLVE_PAGE_SIZE} links you own, and
232
+ * gives up after {@link RESOLVE_MAX_PAGES} pages.
233
+ *
234
+ * @throws {OmsError} with `code === "not_found"` when no owned link carries
235
+ * that endpoint. Nothing was requested by id, so this is not an HTTP 404.
236
+ */
237
+ resolve(endpoint: string, options?: RequestOptions): Promise<ShortLink>;
238
+ /**
239
+ * The public URL a link is served from. Pure string building, no request.
240
+ *
241
+ * The API hands back an `endpoint`, never the address people actually click,
242
+ * so this fills the gap - including for the system namespaces, whose links
243
+ * live one segment deeper.
244
+ */
245
+ shortUrl(link: Pick<ShortLink, "endpoint" | "namespace">): string;
246
+ /** One page of the listing. Shared by {@link list} and {@link resolve}. */
247
+ private fetchPage;
248
+ }