@proveanything/smartlinks 1.14.10 → 1.14.11
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.
- package/dist/api/contact.d.ts +2 -1
- package/dist/api/contact.js +46 -0
- package/dist/docs/API_SUMMARY.md +57 -1
- package/dist/docs/contact-search.md +162 -0
- package/dist/openapi.yaml +171 -0
- package/dist/types/contact.d.ts +26 -0
- package/docs/API_SUMMARY.md +57 -1
- package/docs/contact-search.md +162 -0
- package/openapi.yaml +171 -0
- package/package.json +1 -1
package/dist/api/contact.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ContactResponse, ContactCreateRequest, ContactUpdateRequest, ContactListResponse, PublicContactUpsertRequest, PublicContactUpsertResponse, UserSearchResponse, ContactPatch, PublicGetMyContactResponse, PublicUpdateMyContactResponse, ContactSchemaResponse } from "../types";
|
|
1
|
+
import { ContactResponse, ContactCreateRequest, ContactUpdateRequest, ContactListResponse, ContactSearchParams, ContactSearchResponse, PublicContactUpsertRequest, PublicContactUpsertResponse, UserSearchResponse, ContactPatch, PublicGetMyContactResponse, PublicUpdateMyContactResponse, ContactSchemaResponse } from "../types";
|
|
2
2
|
export declare namespace contact {
|
|
3
3
|
function create(collectionId: string, data: ContactCreateRequest): Promise<ContactResponse>;
|
|
4
4
|
function list(collectionId: string, params?: {
|
|
@@ -6,6 +6,7 @@ export declare namespace contact {
|
|
|
6
6
|
offset?: number;
|
|
7
7
|
includeDeleted?: boolean;
|
|
8
8
|
}): Promise<ContactListResponse>;
|
|
9
|
+
function search({ collectionId, q, typeahead, email, phone, id, userId, tags, tagsAll, source, locale, createdFrom, createdTo, externalIdKey, externalIdValue, customFieldKey, customFieldValue, limit, offset, }: ContactSearchParams): Promise<ContactSearchResponse>;
|
|
9
10
|
function get(collectionId: string, contactId: string, params?: {
|
|
10
11
|
includeDeleted?: boolean;
|
|
11
12
|
}): Promise<ContactResponse>;
|
package/dist/api/contact.js
CHANGED
|
@@ -19,6 +19,52 @@ export var contact;
|
|
|
19
19
|
return request(path);
|
|
20
20
|
}
|
|
21
21
|
contact.list = list;
|
|
22
|
+
async function search({ collectionId, q, typeahead, email, phone, id, userId, tags, tagsAll, source, locale, createdFrom, createdTo, externalIdKey, externalIdValue, customFieldKey, customFieldValue, limit, offset, }) {
|
|
23
|
+
const query = new URLSearchParams();
|
|
24
|
+
if (q !== undefined)
|
|
25
|
+
query.set("q", q);
|
|
26
|
+
if (typeahead !== undefined)
|
|
27
|
+
query.set("typeahead", String(typeahead));
|
|
28
|
+
if (email !== undefined)
|
|
29
|
+
query.set("email", email);
|
|
30
|
+
if (phone !== undefined)
|
|
31
|
+
query.set("phone", phone);
|
|
32
|
+
if (id !== undefined)
|
|
33
|
+
query.set("id", id);
|
|
34
|
+
if (userId !== undefined)
|
|
35
|
+
query.set("userId", userId);
|
|
36
|
+
if (tags !== undefined) {
|
|
37
|
+
const arr = Array.isArray(tags) ? tags : tags.split(",").map(t => t.trim());
|
|
38
|
+
arr.forEach(t => query.append("tags", t));
|
|
39
|
+
}
|
|
40
|
+
if (tagsAll !== undefined) {
|
|
41
|
+
const arr = Array.isArray(tagsAll) ? tagsAll : tagsAll.split(",").map(t => t.trim());
|
|
42
|
+
arr.forEach(t => query.append("tagsAll", t));
|
|
43
|
+
}
|
|
44
|
+
if (source !== undefined)
|
|
45
|
+
query.set("source", source);
|
|
46
|
+
if (locale !== undefined)
|
|
47
|
+
query.set("locale", locale);
|
|
48
|
+
if (createdFrom !== undefined)
|
|
49
|
+
query.set("createdFrom", createdFrom);
|
|
50
|
+
if (createdTo !== undefined)
|
|
51
|
+
query.set("createdTo", createdTo);
|
|
52
|
+
if (externalIdKey !== undefined)
|
|
53
|
+
query.set("externalIdKey", externalIdKey);
|
|
54
|
+
if (externalIdValue !== undefined)
|
|
55
|
+
query.set("externalIdValue", externalIdValue);
|
|
56
|
+
if (customFieldKey !== undefined)
|
|
57
|
+
query.set("customFieldKey", customFieldKey);
|
|
58
|
+
if (customFieldValue !== undefined)
|
|
59
|
+
query.set("customFieldValue", customFieldValue);
|
|
60
|
+
if (limit !== undefined)
|
|
61
|
+
query.set("limit", String(limit));
|
|
62
|
+
if (offset !== undefined)
|
|
63
|
+
query.set("offset", String(offset));
|
|
64
|
+
const path = `/admin/collection/${encodeURIComponent(collectionId)}/contacts/search?${query.toString()}`;
|
|
65
|
+
return request(path);
|
|
66
|
+
}
|
|
67
|
+
contact.search = search;
|
|
22
68
|
async function get(collectionId, contactId, params) {
|
|
23
69
|
const query = new URLSearchParams();
|
|
24
70
|
if ((params === null || params === void 0 ? void 0 : params.includeDeleted) !== undefined)
|
package/dist/docs/API_SUMMARY.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Smartlinks API Summary
|
|
2
2
|
|
|
3
|
-
Version: 1.14.
|
|
3
|
+
Version: 1.14.11 | Generated: 2026-05-19T10:35:51.071Z
|
|
4
4
|
|
|
5
5
|
This is a concise summary of all available API functions and types.
|
|
6
6
|
|
|
@@ -4460,6 +4460,40 @@ interface ContactListResponse {
|
|
|
4460
4460
|
}
|
|
4461
4461
|
```
|
|
4462
4462
|
|
|
4463
|
+
**ContactSearchParams** (interface)
|
|
4464
|
+
```typescript
|
|
4465
|
+
interface ContactSearchParams {
|
|
4466
|
+
collectionId: string
|
|
4467
|
+
q?: string
|
|
4468
|
+
typeahead?: boolean
|
|
4469
|
+
email?: string
|
|
4470
|
+
phone?: string
|
|
4471
|
+
id?: string
|
|
4472
|
+
userId?: string
|
|
4473
|
+
tags?: string | string[]
|
|
4474
|
+
tagsAll?: string | string[]
|
|
4475
|
+
source?: string
|
|
4476
|
+
locale?: string
|
|
4477
|
+
createdFrom?: string
|
|
4478
|
+
createdTo?: string
|
|
4479
|
+
externalIdKey?: string
|
|
4480
|
+
externalIdValue?: string
|
|
4481
|
+
customFieldKey?: string
|
|
4482
|
+
customFieldValue?: string
|
|
4483
|
+
limit?: number
|
|
4484
|
+
offset?: number
|
|
4485
|
+
}
|
|
4486
|
+
```
|
|
4487
|
+
|
|
4488
|
+
**ContactSearchResponse** (interface)
|
|
4489
|
+
```typescript
|
|
4490
|
+
interface ContactSearchResponse {
|
|
4491
|
+
items: Contact[]
|
|
4492
|
+
limit: number
|
|
4493
|
+
offset: number
|
|
4494
|
+
}
|
|
4495
|
+
```
|
|
4496
|
+
|
|
4463
4497
|
**PublicContactUpsertResponse** (interface)
|
|
4464
4498
|
```typescript
|
|
4465
4499
|
interface PublicContactUpsertResponse {
|
|
@@ -8629,6 +8663,28 @@ Returns all proof type definitions. Proof types are templates that specify which
|
|
|
8629
8663
|
**list**(collectionId: string,
|
|
8630
8664
|
params?: { limit?: number; offset?: number; includeDeleted?: boolean }) → `Promise<ContactListResponse>`
|
|
8631
8665
|
|
|
8666
|
+
**search**({
|
|
8667
|
+
collectionId,
|
|
8668
|
+
q,
|
|
8669
|
+
typeahead,
|
|
8670
|
+
email,
|
|
8671
|
+
phone,
|
|
8672
|
+
id,
|
|
8673
|
+
userId,
|
|
8674
|
+
tags,
|
|
8675
|
+
tagsAll,
|
|
8676
|
+
source,
|
|
8677
|
+
locale,
|
|
8678
|
+
createdFrom,
|
|
8679
|
+
createdTo,
|
|
8680
|
+
externalIdKey,
|
|
8681
|
+
externalIdValue,
|
|
8682
|
+
customFieldKey,
|
|
8683
|
+
customFieldValue,
|
|
8684
|
+
limit,
|
|
8685
|
+
offset,
|
|
8686
|
+
}: ContactSearchParams) → `Promise<ContactSearchResponse>`
|
|
8687
|
+
|
|
8632
8688
|
**get**(collectionId: string,
|
|
8633
8689
|
contactId: string,
|
|
8634
8690
|
params?: { includeDeleted?: boolean }) → `Promise<ContactResponse>`
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# Contact Search
|
|
2
|
+
|
|
3
|
+
Admin-scoped endpoint for querying contacts within a collection. Supports
|
|
4
|
+
free-text search, type-ahead, identity lookup, structured filters, and JSONB
|
|
5
|
+
field queries. All parameters are optional and composable.
|
|
6
|
+
|
|
7
|
+
**Base path:** `GET /api/admin/:collectionId/contacts/search`
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Quick examples
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
// Type-ahead while user types "joh" into a search box
|
|
15
|
+
await contact.search({ collectionId, q: "joh", typeahead: true })
|
|
16
|
+
|
|
17
|
+
// Find contacts by partial email
|
|
18
|
+
await contact.search({ collectionId, email: "acme.com" })
|
|
19
|
+
|
|
20
|
+
// Exact contact by UUID
|
|
21
|
+
await contact.search({ collectionId, id: "uuid-here" })
|
|
22
|
+
|
|
23
|
+
// All contacts tagged "vip" created this year
|
|
24
|
+
await contact.search({
|
|
25
|
+
collectionId,
|
|
26
|
+
tags: ["vip"],
|
|
27
|
+
createdFrom: "2026-01-01",
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
// Find by external ID (e.g. Shopify customer ID)
|
|
31
|
+
await contact.search({
|
|
32
|
+
collectionId,
|
|
33
|
+
externalIdKey: "shopify_id",
|
|
34
|
+
externalIdValue: "123456",
|
|
35
|
+
})
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Parameters
|
|
41
|
+
|
|
42
|
+
### Free-text
|
|
43
|
+
|
|
44
|
+
| Param | Type | Description |
|
|
45
|
+
|---|---|---|
|
|
46
|
+
| `q` | `string` | General search term. Searches `first_name`, `last_name`, `display_name`, `company`, and all identity values (`email`, `phone`). When the value contains `@` only email identities are searched; when it matches a phone pattern only phone identities are searched. |
|
|
47
|
+
| `typeahead` | `boolean` | When `true`, uses prefix (`startsWith`) operators instead of substring (`contains`). Significantly cheaper per keystroke. Default limit becomes `10`; minimum `q` length is 2 characters. Use this for live search-as-you-type UIs. |
|
|
48
|
+
|
|
49
|
+
### Identity filters
|
|
50
|
+
|
|
51
|
+
| Param | Type | Description |
|
|
52
|
+
|---|---|---|
|
|
53
|
+
| `email` | `string` | Partial match against normalised email identity values. Case-insensitive. |
|
|
54
|
+
| `phone` | `string` | Partial match against normalised phone identity values. |
|
|
55
|
+
|
|
56
|
+
### Exact lookups
|
|
57
|
+
|
|
58
|
+
These short-circuit all text/filter logic and return at most one contact.
|
|
59
|
+
|
|
60
|
+
| Param | Type | Description |
|
|
61
|
+
|---|---|---|
|
|
62
|
+
| `id` | `string` (UUID) | Contact primary key. |
|
|
63
|
+
| `userId` | `string` | Firebase auth UID linked to the contact. |
|
|
64
|
+
|
|
65
|
+
### Structured filters
|
|
66
|
+
|
|
67
|
+
All filters are ANDed with each other and with any text search.
|
|
68
|
+
|
|
69
|
+
| Param | Type | Description |
|
|
70
|
+
|---|---|---|
|
|
71
|
+
| `tags` | `string \| string[]` | Comma-separated string or repeated param. Contacts must have **any** of these tags. |
|
|
72
|
+
| `tagsAll` | `string \| string[]` | Contacts must have **all** of these tags. |
|
|
73
|
+
| `source` | `string` | Exact match on the `source` field. |
|
|
74
|
+
| `locale` | `string` | Exact match on the `locale` field (e.g. `"en-US"`). |
|
|
75
|
+
| `createdFrom` | `string` (ISO-8601) | Lower bound on `created_at`. |
|
|
76
|
+
| `createdTo` | `string` (ISO-8601) | Upper bound on `created_at`. |
|
|
77
|
+
|
|
78
|
+
### JSONB field filters
|
|
79
|
+
|
|
80
|
+
Searches inside the `external_ids` or `custom_fields` JSONB columns by
|
|
81
|
+
key/value pair. Both params in a pair must be supplied together.
|
|
82
|
+
|
|
83
|
+
| Param | Type | Description |
|
|
84
|
+
|---|---|---|
|
|
85
|
+
| `externalIdKey` | `string` | Top-level key in `externalIds` (e.g. `"shopify_id"`). |
|
|
86
|
+
| `externalIdValue` | `string` | Expected value at that key. |
|
|
87
|
+
| `customFieldKey` | `string` | Top-level key in `customFields`. |
|
|
88
|
+
| `customFieldValue` | `string` | Expected value at that key. |
|
|
89
|
+
|
|
90
|
+
### Pagination
|
|
91
|
+
|
|
92
|
+
| Param | Type | Default | Max |
|
|
93
|
+
|---|---|---|---|
|
|
94
|
+
| `limit` | `number` | `20` (`10` in typeahead mode) | `100` (`10` in typeahead mode) |
|
|
95
|
+
| `offset` | `number` | `0` | — |
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## Response
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
{
|
|
103
|
+
items: Contact[],
|
|
104
|
+
limit: number,
|
|
105
|
+
offset: number,
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Each `Contact` item follows the shape defined in `src/types/contact.ts`.
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## SDK usage
|
|
114
|
+
|
|
115
|
+
```ts
|
|
116
|
+
import { contact } from '@proveanything/smartlinks'
|
|
117
|
+
|
|
118
|
+
const results = await contact.search({
|
|
119
|
+
collectionId: "my-collection",
|
|
120
|
+
q: "jane",
|
|
121
|
+
tags: ["vip"],
|
|
122
|
+
limit: 20,
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
for (const c of results.items) {
|
|
126
|
+
console.log(c.displayName, c.email)
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## Performance notes
|
|
133
|
+
|
|
134
|
+
The search endpoint is backed by **PostgreSQL trigram GIN indexes** via the
|
|
135
|
+
`pg_trgm` extension (migration `20260519000001`). This makes `ILIKE '%query%'`
|
|
136
|
+
scans fast even with millions of contacts per org.
|
|
137
|
+
|
|
138
|
+
**What is a trigram?** PostgreSQL decomposes every string into overlapping
|
|
139
|
+
3-character windows ("trigrams") and stores them in an inverted index. A query
|
|
140
|
+
for `"acme"` is converted into trigrams `" ac", "acm", "cme", "me "` and the
|
|
141
|
+
index returns only rows containing those windows — so Postgres never scans rows
|
|
142
|
+
that can't match, regardless of whether the query is a prefix, suffix, or
|
|
143
|
+
middle-of-word substring.
|
|
144
|
+
|
|
145
|
+
**Type-ahead mode** (`typeahead: true`) is the recommended pattern for
|
|
146
|
+
keystroke-by-keystroke UI. It emits `LIKE 'query%'` (prefix) operators instead
|
|
147
|
+
of `LIKE '%query%'` (substring). Prefix scans are cheaper because they require
|
|
148
|
+
fewer trigram lookups, making them safe to call on every keypress. Minimum query
|
|
149
|
+
length is enforced at 2 characters server-side to prevent over-broad scans.
|
|
150
|
+
|
|
151
|
+
**Future scale path:** At very high volumes, routing `search` through
|
|
152
|
+
Elasticsearch would add typo-tolerance and relevance ranking. The API surface is
|
|
153
|
+
intentionally implementation-agnostic so the backing engine can be swapped
|
|
154
|
+
without client changes.
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## Error codes
|
|
159
|
+
|
|
160
|
+
| Code | HTTP | Meaning |
|
|
161
|
+
|---|---|---|
|
|
162
|
+
| `SEARCH_REQUIRED` | 400 | No searchable parameter was provided. |
|
package/dist/openapi.yaml
CHANGED
|
@@ -2586,6 +2586,113 @@ paths:
|
|
|
2586
2586
|
description: Unauthorized
|
|
2587
2587
|
404:
|
|
2588
2588
|
description: Not found
|
|
2589
|
+
/admin/collection/{collectionId}/contacts/search:
|
|
2590
|
+
get:
|
|
2591
|
+
tags:
|
|
2592
|
+
- contact
|
|
2593
|
+
summary: contact.search
|
|
2594
|
+
operationId: contact_search
|
|
2595
|
+
security:
|
|
2596
|
+
- bearerAuth: []
|
|
2597
|
+
parameters:
|
|
2598
|
+
- name: collectionId
|
|
2599
|
+
in: path
|
|
2600
|
+
required: true
|
|
2601
|
+
schema:
|
|
2602
|
+
type: string
|
|
2603
|
+
- name: q
|
|
2604
|
+
in: query
|
|
2605
|
+
required: false
|
|
2606
|
+
schema:
|
|
2607
|
+
type: string
|
|
2608
|
+
- name: typeahead
|
|
2609
|
+
in: query
|
|
2610
|
+
required: false
|
|
2611
|
+
schema:
|
|
2612
|
+
type: string
|
|
2613
|
+
- name: email
|
|
2614
|
+
in: query
|
|
2615
|
+
required: false
|
|
2616
|
+
schema:
|
|
2617
|
+
type: string
|
|
2618
|
+
- name: phone
|
|
2619
|
+
in: query
|
|
2620
|
+
required: false
|
|
2621
|
+
schema:
|
|
2622
|
+
type: string
|
|
2623
|
+
- name: id
|
|
2624
|
+
in: query
|
|
2625
|
+
required: false
|
|
2626
|
+
schema:
|
|
2627
|
+
type: string
|
|
2628
|
+
- name: userId
|
|
2629
|
+
in: query
|
|
2630
|
+
required: false
|
|
2631
|
+
schema:
|
|
2632
|
+
type: string
|
|
2633
|
+
- name: source
|
|
2634
|
+
in: query
|
|
2635
|
+
required: false
|
|
2636
|
+
schema:
|
|
2637
|
+
type: string
|
|
2638
|
+
- name: locale
|
|
2639
|
+
in: query
|
|
2640
|
+
required: false
|
|
2641
|
+
schema:
|
|
2642
|
+
type: string
|
|
2643
|
+
- name: createdFrom
|
|
2644
|
+
in: query
|
|
2645
|
+
required: false
|
|
2646
|
+
schema:
|
|
2647
|
+
type: string
|
|
2648
|
+
- name: createdTo
|
|
2649
|
+
in: query
|
|
2650
|
+
required: false
|
|
2651
|
+
schema:
|
|
2652
|
+
type: string
|
|
2653
|
+
- name: externalIdKey
|
|
2654
|
+
in: query
|
|
2655
|
+
required: false
|
|
2656
|
+
schema:
|
|
2657
|
+
type: string
|
|
2658
|
+
- name: externalIdValue
|
|
2659
|
+
in: query
|
|
2660
|
+
required: false
|
|
2661
|
+
schema:
|
|
2662
|
+
type: string
|
|
2663
|
+
- name: customFieldKey
|
|
2664
|
+
in: query
|
|
2665
|
+
required: false
|
|
2666
|
+
schema:
|
|
2667
|
+
type: string
|
|
2668
|
+
- name: customFieldValue
|
|
2669
|
+
in: query
|
|
2670
|
+
required: false
|
|
2671
|
+
schema:
|
|
2672
|
+
type: string
|
|
2673
|
+
- name: limit
|
|
2674
|
+
in: query
|
|
2675
|
+
required: false
|
|
2676
|
+
schema:
|
|
2677
|
+
type: string
|
|
2678
|
+
- name: offset
|
|
2679
|
+
in: query
|
|
2680
|
+
required: false
|
|
2681
|
+
schema:
|
|
2682
|
+
type: string
|
|
2683
|
+
responses:
|
|
2684
|
+
200:
|
|
2685
|
+
description: Success
|
|
2686
|
+
content:
|
|
2687
|
+
application/json:
|
|
2688
|
+
schema:
|
|
2689
|
+
$ref: "#/components/schemas/ContactSearchResponse"
|
|
2690
|
+
400:
|
|
2691
|
+
description: Bad request
|
|
2692
|
+
401:
|
|
2693
|
+
description: Unauthorized
|
|
2694
|
+
404:
|
|
2695
|
+
description: Not found
|
|
2589
2696
|
/admin/collection/{collectionId}/contacts/upsert:
|
|
2590
2697
|
post:
|
|
2591
2698
|
tags:
|
|
@@ -19580,6 +19687,70 @@ components:
|
|
|
19580
19687
|
- items
|
|
19581
19688
|
- limit
|
|
19582
19689
|
- offset
|
|
19690
|
+
ContactSearchParams:
|
|
19691
|
+
type: object
|
|
19692
|
+
properties:
|
|
19693
|
+
collectionId:
|
|
19694
|
+
type: string
|
|
19695
|
+
q:
|
|
19696
|
+
type: string
|
|
19697
|
+
typeahead:
|
|
19698
|
+
type: boolean
|
|
19699
|
+
email:
|
|
19700
|
+
type: string
|
|
19701
|
+
phone:
|
|
19702
|
+
type: string
|
|
19703
|
+
id:
|
|
19704
|
+
type: string
|
|
19705
|
+
userId:
|
|
19706
|
+
type: string
|
|
19707
|
+
tags:
|
|
19708
|
+
type: array
|
|
19709
|
+
items:
|
|
19710
|
+
type: object
|
|
19711
|
+
additionalProperties: true
|
|
19712
|
+
tagsAll:
|
|
19713
|
+
type: array
|
|
19714
|
+
items:
|
|
19715
|
+
type: object
|
|
19716
|
+
additionalProperties: true
|
|
19717
|
+
source:
|
|
19718
|
+
type: string
|
|
19719
|
+
locale:
|
|
19720
|
+
type: string
|
|
19721
|
+
createdFrom:
|
|
19722
|
+
type: string
|
|
19723
|
+
createdTo:
|
|
19724
|
+
type: string
|
|
19725
|
+
externalIdKey:
|
|
19726
|
+
type: string
|
|
19727
|
+
externalIdValue:
|
|
19728
|
+
type: string
|
|
19729
|
+
customFieldKey:
|
|
19730
|
+
type: string
|
|
19731
|
+
customFieldValue:
|
|
19732
|
+
type: string
|
|
19733
|
+
limit:
|
|
19734
|
+
type: number
|
|
19735
|
+
offset:
|
|
19736
|
+
type: number
|
|
19737
|
+
required:
|
|
19738
|
+
- collectionId
|
|
19739
|
+
ContactSearchResponse:
|
|
19740
|
+
type: object
|
|
19741
|
+
properties:
|
|
19742
|
+
items:
|
|
19743
|
+
type: array
|
|
19744
|
+
items:
|
|
19745
|
+
$ref: "#/components/schemas/Contact"
|
|
19746
|
+
limit:
|
|
19747
|
+
type: number
|
|
19748
|
+
offset:
|
|
19749
|
+
type: number
|
|
19750
|
+
required:
|
|
19751
|
+
- items
|
|
19752
|
+
- limit
|
|
19753
|
+
- offset
|
|
19583
19754
|
PublicContactUpsertResponse:
|
|
19584
19755
|
type: object
|
|
19585
19756
|
properties:
|
package/dist/types/contact.d.ts
CHANGED
|
@@ -34,6 +34,32 @@ export interface ContactListResponse {
|
|
|
34
34
|
limit: number;
|
|
35
35
|
offset: number;
|
|
36
36
|
}
|
|
37
|
+
export interface ContactSearchParams {
|
|
38
|
+
collectionId: string;
|
|
39
|
+
q?: string;
|
|
40
|
+
typeahead?: boolean;
|
|
41
|
+
email?: string;
|
|
42
|
+
phone?: string;
|
|
43
|
+
id?: string;
|
|
44
|
+
userId?: string;
|
|
45
|
+
tags?: string | string[];
|
|
46
|
+
tagsAll?: string | string[];
|
|
47
|
+
source?: string;
|
|
48
|
+
locale?: string;
|
|
49
|
+
createdFrom?: string;
|
|
50
|
+
createdTo?: string;
|
|
51
|
+
externalIdKey?: string;
|
|
52
|
+
externalIdValue?: string;
|
|
53
|
+
customFieldKey?: string;
|
|
54
|
+
customFieldValue?: string;
|
|
55
|
+
limit?: number;
|
|
56
|
+
offset?: number;
|
|
57
|
+
}
|
|
58
|
+
export interface ContactSearchResponse {
|
|
59
|
+
items: Contact[];
|
|
60
|
+
limit: number;
|
|
61
|
+
offset: number;
|
|
62
|
+
}
|
|
37
63
|
export type PublicContactUpsertRequest = Partial<Pick<Contact, "email" | "phone" | "userId" | "firstName" | "lastName" | "displayName" | "company" | "tags" | "source" | "notes" | "avatarUrl" | "locale" | "timezone" | "externalIds">> & {
|
|
38
64
|
customFields?: ContactCustomFields;
|
|
39
65
|
};
|
package/docs/API_SUMMARY.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Smartlinks API Summary
|
|
2
2
|
|
|
3
|
-
Version: 1.14.
|
|
3
|
+
Version: 1.14.11 | Generated: 2026-05-19T10:35:51.071Z
|
|
4
4
|
|
|
5
5
|
This is a concise summary of all available API functions and types.
|
|
6
6
|
|
|
@@ -4460,6 +4460,40 @@ interface ContactListResponse {
|
|
|
4460
4460
|
}
|
|
4461
4461
|
```
|
|
4462
4462
|
|
|
4463
|
+
**ContactSearchParams** (interface)
|
|
4464
|
+
```typescript
|
|
4465
|
+
interface ContactSearchParams {
|
|
4466
|
+
collectionId: string
|
|
4467
|
+
q?: string
|
|
4468
|
+
typeahead?: boolean
|
|
4469
|
+
email?: string
|
|
4470
|
+
phone?: string
|
|
4471
|
+
id?: string
|
|
4472
|
+
userId?: string
|
|
4473
|
+
tags?: string | string[]
|
|
4474
|
+
tagsAll?: string | string[]
|
|
4475
|
+
source?: string
|
|
4476
|
+
locale?: string
|
|
4477
|
+
createdFrom?: string
|
|
4478
|
+
createdTo?: string
|
|
4479
|
+
externalIdKey?: string
|
|
4480
|
+
externalIdValue?: string
|
|
4481
|
+
customFieldKey?: string
|
|
4482
|
+
customFieldValue?: string
|
|
4483
|
+
limit?: number
|
|
4484
|
+
offset?: number
|
|
4485
|
+
}
|
|
4486
|
+
```
|
|
4487
|
+
|
|
4488
|
+
**ContactSearchResponse** (interface)
|
|
4489
|
+
```typescript
|
|
4490
|
+
interface ContactSearchResponse {
|
|
4491
|
+
items: Contact[]
|
|
4492
|
+
limit: number
|
|
4493
|
+
offset: number
|
|
4494
|
+
}
|
|
4495
|
+
```
|
|
4496
|
+
|
|
4463
4497
|
**PublicContactUpsertResponse** (interface)
|
|
4464
4498
|
```typescript
|
|
4465
4499
|
interface PublicContactUpsertResponse {
|
|
@@ -8629,6 +8663,28 @@ Returns all proof type definitions. Proof types are templates that specify which
|
|
|
8629
8663
|
**list**(collectionId: string,
|
|
8630
8664
|
params?: { limit?: number; offset?: number; includeDeleted?: boolean }) → `Promise<ContactListResponse>`
|
|
8631
8665
|
|
|
8666
|
+
**search**({
|
|
8667
|
+
collectionId,
|
|
8668
|
+
q,
|
|
8669
|
+
typeahead,
|
|
8670
|
+
email,
|
|
8671
|
+
phone,
|
|
8672
|
+
id,
|
|
8673
|
+
userId,
|
|
8674
|
+
tags,
|
|
8675
|
+
tagsAll,
|
|
8676
|
+
source,
|
|
8677
|
+
locale,
|
|
8678
|
+
createdFrom,
|
|
8679
|
+
createdTo,
|
|
8680
|
+
externalIdKey,
|
|
8681
|
+
externalIdValue,
|
|
8682
|
+
customFieldKey,
|
|
8683
|
+
customFieldValue,
|
|
8684
|
+
limit,
|
|
8685
|
+
offset,
|
|
8686
|
+
}: ContactSearchParams) → `Promise<ContactSearchResponse>`
|
|
8687
|
+
|
|
8632
8688
|
**get**(collectionId: string,
|
|
8633
8689
|
contactId: string,
|
|
8634
8690
|
params?: { includeDeleted?: boolean }) → `Promise<ContactResponse>`
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# Contact Search
|
|
2
|
+
|
|
3
|
+
Admin-scoped endpoint for querying contacts within a collection. Supports
|
|
4
|
+
free-text search, type-ahead, identity lookup, structured filters, and JSONB
|
|
5
|
+
field queries. All parameters are optional and composable.
|
|
6
|
+
|
|
7
|
+
**Base path:** `GET /api/admin/:collectionId/contacts/search`
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Quick examples
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
// Type-ahead while user types "joh" into a search box
|
|
15
|
+
await contact.search({ collectionId, q: "joh", typeahead: true })
|
|
16
|
+
|
|
17
|
+
// Find contacts by partial email
|
|
18
|
+
await contact.search({ collectionId, email: "acme.com" })
|
|
19
|
+
|
|
20
|
+
// Exact contact by UUID
|
|
21
|
+
await contact.search({ collectionId, id: "uuid-here" })
|
|
22
|
+
|
|
23
|
+
// All contacts tagged "vip" created this year
|
|
24
|
+
await contact.search({
|
|
25
|
+
collectionId,
|
|
26
|
+
tags: ["vip"],
|
|
27
|
+
createdFrom: "2026-01-01",
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
// Find by external ID (e.g. Shopify customer ID)
|
|
31
|
+
await contact.search({
|
|
32
|
+
collectionId,
|
|
33
|
+
externalIdKey: "shopify_id",
|
|
34
|
+
externalIdValue: "123456",
|
|
35
|
+
})
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Parameters
|
|
41
|
+
|
|
42
|
+
### Free-text
|
|
43
|
+
|
|
44
|
+
| Param | Type | Description |
|
|
45
|
+
|---|---|---|
|
|
46
|
+
| `q` | `string` | General search term. Searches `first_name`, `last_name`, `display_name`, `company`, and all identity values (`email`, `phone`). When the value contains `@` only email identities are searched; when it matches a phone pattern only phone identities are searched. |
|
|
47
|
+
| `typeahead` | `boolean` | When `true`, uses prefix (`startsWith`) operators instead of substring (`contains`). Significantly cheaper per keystroke. Default limit becomes `10`; minimum `q` length is 2 characters. Use this for live search-as-you-type UIs. |
|
|
48
|
+
|
|
49
|
+
### Identity filters
|
|
50
|
+
|
|
51
|
+
| Param | Type | Description |
|
|
52
|
+
|---|---|---|
|
|
53
|
+
| `email` | `string` | Partial match against normalised email identity values. Case-insensitive. |
|
|
54
|
+
| `phone` | `string` | Partial match against normalised phone identity values. |
|
|
55
|
+
|
|
56
|
+
### Exact lookups
|
|
57
|
+
|
|
58
|
+
These short-circuit all text/filter logic and return at most one contact.
|
|
59
|
+
|
|
60
|
+
| Param | Type | Description |
|
|
61
|
+
|---|---|---|
|
|
62
|
+
| `id` | `string` (UUID) | Contact primary key. |
|
|
63
|
+
| `userId` | `string` | Firebase auth UID linked to the contact. |
|
|
64
|
+
|
|
65
|
+
### Structured filters
|
|
66
|
+
|
|
67
|
+
All filters are ANDed with each other and with any text search.
|
|
68
|
+
|
|
69
|
+
| Param | Type | Description |
|
|
70
|
+
|---|---|---|
|
|
71
|
+
| `tags` | `string \| string[]` | Comma-separated string or repeated param. Contacts must have **any** of these tags. |
|
|
72
|
+
| `tagsAll` | `string \| string[]` | Contacts must have **all** of these tags. |
|
|
73
|
+
| `source` | `string` | Exact match on the `source` field. |
|
|
74
|
+
| `locale` | `string` | Exact match on the `locale` field (e.g. `"en-US"`). |
|
|
75
|
+
| `createdFrom` | `string` (ISO-8601) | Lower bound on `created_at`. |
|
|
76
|
+
| `createdTo` | `string` (ISO-8601) | Upper bound on `created_at`. |
|
|
77
|
+
|
|
78
|
+
### JSONB field filters
|
|
79
|
+
|
|
80
|
+
Searches inside the `external_ids` or `custom_fields` JSONB columns by
|
|
81
|
+
key/value pair. Both params in a pair must be supplied together.
|
|
82
|
+
|
|
83
|
+
| Param | Type | Description |
|
|
84
|
+
|---|---|---|
|
|
85
|
+
| `externalIdKey` | `string` | Top-level key in `externalIds` (e.g. `"shopify_id"`). |
|
|
86
|
+
| `externalIdValue` | `string` | Expected value at that key. |
|
|
87
|
+
| `customFieldKey` | `string` | Top-level key in `customFields`. |
|
|
88
|
+
| `customFieldValue` | `string` | Expected value at that key. |
|
|
89
|
+
|
|
90
|
+
### Pagination
|
|
91
|
+
|
|
92
|
+
| Param | Type | Default | Max |
|
|
93
|
+
|---|---|---|---|
|
|
94
|
+
| `limit` | `number` | `20` (`10` in typeahead mode) | `100` (`10` in typeahead mode) |
|
|
95
|
+
| `offset` | `number` | `0` | — |
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## Response
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
{
|
|
103
|
+
items: Contact[],
|
|
104
|
+
limit: number,
|
|
105
|
+
offset: number,
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Each `Contact` item follows the shape defined in `src/types/contact.ts`.
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## SDK usage
|
|
114
|
+
|
|
115
|
+
```ts
|
|
116
|
+
import { contact } from '@proveanything/smartlinks'
|
|
117
|
+
|
|
118
|
+
const results = await contact.search({
|
|
119
|
+
collectionId: "my-collection",
|
|
120
|
+
q: "jane",
|
|
121
|
+
tags: ["vip"],
|
|
122
|
+
limit: 20,
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
for (const c of results.items) {
|
|
126
|
+
console.log(c.displayName, c.email)
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## Performance notes
|
|
133
|
+
|
|
134
|
+
The search endpoint is backed by **PostgreSQL trigram GIN indexes** via the
|
|
135
|
+
`pg_trgm` extension (migration `20260519000001`). This makes `ILIKE '%query%'`
|
|
136
|
+
scans fast even with millions of contacts per org.
|
|
137
|
+
|
|
138
|
+
**What is a trigram?** PostgreSQL decomposes every string into overlapping
|
|
139
|
+
3-character windows ("trigrams") and stores them in an inverted index. A query
|
|
140
|
+
for `"acme"` is converted into trigrams `" ac", "acm", "cme", "me "` and the
|
|
141
|
+
index returns only rows containing those windows — so Postgres never scans rows
|
|
142
|
+
that can't match, regardless of whether the query is a prefix, suffix, or
|
|
143
|
+
middle-of-word substring.
|
|
144
|
+
|
|
145
|
+
**Type-ahead mode** (`typeahead: true`) is the recommended pattern for
|
|
146
|
+
keystroke-by-keystroke UI. It emits `LIKE 'query%'` (prefix) operators instead
|
|
147
|
+
of `LIKE '%query%'` (substring). Prefix scans are cheaper because they require
|
|
148
|
+
fewer trigram lookups, making them safe to call on every keypress. Minimum query
|
|
149
|
+
length is enforced at 2 characters server-side to prevent over-broad scans.
|
|
150
|
+
|
|
151
|
+
**Future scale path:** At very high volumes, routing `search` through
|
|
152
|
+
Elasticsearch would add typo-tolerance and relevance ranking. The API surface is
|
|
153
|
+
intentionally implementation-agnostic so the backing engine can be swapped
|
|
154
|
+
without client changes.
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## Error codes
|
|
159
|
+
|
|
160
|
+
| Code | HTTP | Meaning |
|
|
161
|
+
|---|---|---|
|
|
162
|
+
| `SEARCH_REQUIRED` | 400 | No searchable parameter was provided. |
|
package/openapi.yaml
CHANGED
|
@@ -2586,6 +2586,113 @@ paths:
|
|
|
2586
2586
|
description: Unauthorized
|
|
2587
2587
|
404:
|
|
2588
2588
|
description: Not found
|
|
2589
|
+
/admin/collection/{collectionId}/contacts/search:
|
|
2590
|
+
get:
|
|
2591
|
+
tags:
|
|
2592
|
+
- contact
|
|
2593
|
+
summary: contact.search
|
|
2594
|
+
operationId: contact_search
|
|
2595
|
+
security:
|
|
2596
|
+
- bearerAuth: []
|
|
2597
|
+
parameters:
|
|
2598
|
+
- name: collectionId
|
|
2599
|
+
in: path
|
|
2600
|
+
required: true
|
|
2601
|
+
schema:
|
|
2602
|
+
type: string
|
|
2603
|
+
- name: q
|
|
2604
|
+
in: query
|
|
2605
|
+
required: false
|
|
2606
|
+
schema:
|
|
2607
|
+
type: string
|
|
2608
|
+
- name: typeahead
|
|
2609
|
+
in: query
|
|
2610
|
+
required: false
|
|
2611
|
+
schema:
|
|
2612
|
+
type: string
|
|
2613
|
+
- name: email
|
|
2614
|
+
in: query
|
|
2615
|
+
required: false
|
|
2616
|
+
schema:
|
|
2617
|
+
type: string
|
|
2618
|
+
- name: phone
|
|
2619
|
+
in: query
|
|
2620
|
+
required: false
|
|
2621
|
+
schema:
|
|
2622
|
+
type: string
|
|
2623
|
+
- name: id
|
|
2624
|
+
in: query
|
|
2625
|
+
required: false
|
|
2626
|
+
schema:
|
|
2627
|
+
type: string
|
|
2628
|
+
- name: userId
|
|
2629
|
+
in: query
|
|
2630
|
+
required: false
|
|
2631
|
+
schema:
|
|
2632
|
+
type: string
|
|
2633
|
+
- name: source
|
|
2634
|
+
in: query
|
|
2635
|
+
required: false
|
|
2636
|
+
schema:
|
|
2637
|
+
type: string
|
|
2638
|
+
- name: locale
|
|
2639
|
+
in: query
|
|
2640
|
+
required: false
|
|
2641
|
+
schema:
|
|
2642
|
+
type: string
|
|
2643
|
+
- name: createdFrom
|
|
2644
|
+
in: query
|
|
2645
|
+
required: false
|
|
2646
|
+
schema:
|
|
2647
|
+
type: string
|
|
2648
|
+
- name: createdTo
|
|
2649
|
+
in: query
|
|
2650
|
+
required: false
|
|
2651
|
+
schema:
|
|
2652
|
+
type: string
|
|
2653
|
+
- name: externalIdKey
|
|
2654
|
+
in: query
|
|
2655
|
+
required: false
|
|
2656
|
+
schema:
|
|
2657
|
+
type: string
|
|
2658
|
+
- name: externalIdValue
|
|
2659
|
+
in: query
|
|
2660
|
+
required: false
|
|
2661
|
+
schema:
|
|
2662
|
+
type: string
|
|
2663
|
+
- name: customFieldKey
|
|
2664
|
+
in: query
|
|
2665
|
+
required: false
|
|
2666
|
+
schema:
|
|
2667
|
+
type: string
|
|
2668
|
+
- name: customFieldValue
|
|
2669
|
+
in: query
|
|
2670
|
+
required: false
|
|
2671
|
+
schema:
|
|
2672
|
+
type: string
|
|
2673
|
+
- name: limit
|
|
2674
|
+
in: query
|
|
2675
|
+
required: false
|
|
2676
|
+
schema:
|
|
2677
|
+
type: string
|
|
2678
|
+
- name: offset
|
|
2679
|
+
in: query
|
|
2680
|
+
required: false
|
|
2681
|
+
schema:
|
|
2682
|
+
type: string
|
|
2683
|
+
responses:
|
|
2684
|
+
200:
|
|
2685
|
+
description: Success
|
|
2686
|
+
content:
|
|
2687
|
+
application/json:
|
|
2688
|
+
schema:
|
|
2689
|
+
$ref: "#/components/schemas/ContactSearchResponse"
|
|
2690
|
+
400:
|
|
2691
|
+
description: Bad request
|
|
2692
|
+
401:
|
|
2693
|
+
description: Unauthorized
|
|
2694
|
+
404:
|
|
2695
|
+
description: Not found
|
|
2589
2696
|
/admin/collection/{collectionId}/contacts/upsert:
|
|
2590
2697
|
post:
|
|
2591
2698
|
tags:
|
|
@@ -19580,6 +19687,70 @@ components:
|
|
|
19580
19687
|
- items
|
|
19581
19688
|
- limit
|
|
19582
19689
|
- offset
|
|
19690
|
+
ContactSearchParams:
|
|
19691
|
+
type: object
|
|
19692
|
+
properties:
|
|
19693
|
+
collectionId:
|
|
19694
|
+
type: string
|
|
19695
|
+
q:
|
|
19696
|
+
type: string
|
|
19697
|
+
typeahead:
|
|
19698
|
+
type: boolean
|
|
19699
|
+
email:
|
|
19700
|
+
type: string
|
|
19701
|
+
phone:
|
|
19702
|
+
type: string
|
|
19703
|
+
id:
|
|
19704
|
+
type: string
|
|
19705
|
+
userId:
|
|
19706
|
+
type: string
|
|
19707
|
+
tags:
|
|
19708
|
+
type: array
|
|
19709
|
+
items:
|
|
19710
|
+
type: object
|
|
19711
|
+
additionalProperties: true
|
|
19712
|
+
tagsAll:
|
|
19713
|
+
type: array
|
|
19714
|
+
items:
|
|
19715
|
+
type: object
|
|
19716
|
+
additionalProperties: true
|
|
19717
|
+
source:
|
|
19718
|
+
type: string
|
|
19719
|
+
locale:
|
|
19720
|
+
type: string
|
|
19721
|
+
createdFrom:
|
|
19722
|
+
type: string
|
|
19723
|
+
createdTo:
|
|
19724
|
+
type: string
|
|
19725
|
+
externalIdKey:
|
|
19726
|
+
type: string
|
|
19727
|
+
externalIdValue:
|
|
19728
|
+
type: string
|
|
19729
|
+
customFieldKey:
|
|
19730
|
+
type: string
|
|
19731
|
+
customFieldValue:
|
|
19732
|
+
type: string
|
|
19733
|
+
limit:
|
|
19734
|
+
type: number
|
|
19735
|
+
offset:
|
|
19736
|
+
type: number
|
|
19737
|
+
required:
|
|
19738
|
+
- collectionId
|
|
19739
|
+
ContactSearchResponse:
|
|
19740
|
+
type: object
|
|
19741
|
+
properties:
|
|
19742
|
+
items:
|
|
19743
|
+
type: array
|
|
19744
|
+
items:
|
|
19745
|
+
$ref: "#/components/schemas/Contact"
|
|
19746
|
+
limit:
|
|
19747
|
+
type: number
|
|
19748
|
+
offset:
|
|
19749
|
+
type: number
|
|
19750
|
+
required:
|
|
19751
|
+
- items
|
|
19752
|
+
- limit
|
|
19753
|
+
- offset
|
|
19583
19754
|
PublicContactUpsertResponse:
|
|
19584
19755
|
type: object
|
|
19585
19756
|
properties:
|