vybekiit 0.7.32 → 0.7.33
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/bin.js +5 -2
- package/dist/global-skills/aws-serverless/SKILL.md +168 -32
- package/dist/global-skills/aws-serverless/references/api-gateway.md +17 -0
- package/dist/global-skills/aws-serverless/references/architecture.md +9 -13
- package/dist/global-skills/aws-serverless/references/lambda.md +3 -3
- package/dist/global-skills/aws-serverless/references/orchestration.md +46 -20
- package/dist/global-skills/aws-serverless/references/production.md +1 -1
- package/dist/global-skills/eas-simulator/SKILL.md +15 -15
- package/dist/global-skills/eas-simulator/references/controllers.md +11 -7
- package/dist/global-skills/eas-simulator/references/run-your-app.md +8 -8
- package/dist/global-skills/eas-simulator/references/troubleshooting.md +10 -6
- package/dist/global-skills/email-best-practices/references/sending-reliability.md +17 -22
- package/dist/global-skills/extension-to-functions-codebase/SKILL.md +1 -0
- package/dist/global-skills/firebase-ai-logic-basics/SKILL.md +161 -76
- package/dist/global-skills/firebase-ai-logic-basics/references/flutter_setup.md +37 -0
- package/dist/global-skills/firebase-ai-logic-basics/references/ios_setup.md +97 -3
- package/dist/global-skills/firebase-ai-logic-basics/references/usage_patterns_android.md +88 -3
- package/dist/global-skills/firebase-ai-logic-basics/references/usage_patterns_web.md +91 -3
- package/dist/global-skills/firebase-app-hosting-basics/SKILL.md +5 -1
- package/dist/global-skills/firebase-auth-basics/SKILL.md +1 -0
- package/dist/global-skills/firebase-auth-basics/references/client_sdk_android.md +9 -9
- package/dist/global-skills/firebase-auth-basics/references/client_sdk_web.md +3 -3
- package/dist/global-skills/firebase-auth-basics/references/flutter_setup.md +22 -19
- package/dist/global-skills/firebase-auth-basics/references/security_rules.md +2 -2
- package/dist/global-skills/firebase-basics/SKILL.md +6 -2
- package/dist/global-skills/firebase-basics/references/android_setup.md +101 -37
- package/dist/global-skills/firebase-basics/references/ios_setup.md +57 -0
- package/dist/global-skills/firebase-basics/references/web_setup.md +52 -0
- package/dist/global-skills/firebase-crashlytics/SKILL.md +1 -0
- package/dist/global-skills/firebase-crashlytics/references/android_setup.md +84 -84
- package/dist/global-skills/firebase-crashlytics/references/ios_setup.md +1 -0
- package/dist/global-skills/firebase-data-connect/SKILL.md +1 -0
- package/dist/global-skills/firebase-data-connect/reference/config.md +8 -8
- package/dist/global-skills/firebase-data-connect/reference/realtime.md +4 -1
- package/dist/global-skills/firebase-data-connect/reference/sdk_android.md +33 -34
- package/dist/global-skills/firebase-data-connect/reference/sdk_web.md +8 -3
- package/dist/global-skills/firebase-data-connect/reference/security.md +13 -13
- package/dist/global-skills/firebase-firestore/SKILL.md +17 -7
- package/dist/global-skills/firebase-firestore/references/enterprise/android_sdk_usage.md +32 -32
- package/dist/global-skills/firebase-firestore/references/enterprise/indexes.md +2 -1
- package/dist/global-skills/firebase-firestore/references/standard/android_sdk_usage.md +14 -14
- package/dist/global-skills/firebase-firestore/references/standard/indexes.md +2 -1
- package/dist/global-skills/firebase-hosting-basics/SKILL.md +1 -0
- package/dist/global-skills/firebase-remote-config-basics/SKILL.md +2 -1
- package/dist/global-skills/firebase-remote-config-basics/references/android_setup.md +36 -37
- package/dist/global-skills/firebase-security-rules-auditor/SKILL.md +7 -6
- package/dist/global-skills/firestore-rules-creation/SKILL.md +22 -10
- package/dist/global-skills/neon/SKILL.md +16 -0
- package/dist/global-skills/neon-ai-gateway/SKILL.md +43 -14
- package/dist/global-skills/neon-postgres/SKILL.md +1 -1
- package/dist/global-skills/neon-postgres/references/full-text-search.md +27 -17
- package/dist/global-skills/neon-postgres/references/hybrid-search.md +19 -15
- package/dist/global-skills/neon-postgres/references/lakebase-search-drizzle.md +99 -14
- package/dist/global-skills/neon-postgres/references/vector-search.md +93 -10
- package/dist/global-skills/resend/SKILL.md +4 -15
- package/dist/global-skills/resend/references/domains.md +1 -1
- package/dist/global-skills/resend/references/installation.md +3 -3
- package/dist/global-skills/resend/references/sending/best-practices.md +3 -3
- package/dist/global-skills/resend/references/sending/single-email-examples.md +3 -3
- package/dist/global-skills/stripe-best-practices/SKILL.md +18 -18
- package/dist/global-skills/stripe-best-practices/references/tax.md +26 -14
- package/dist/global-skills/use-railway/SKILL.md +6 -6
- package/dist/global-skills/use-railway/references/iac.md +2 -1
- package/dist/global-skills/use-railway/references/tracing.md +13 -5
- package/dist/global-skills/workers-best-practices/SKILL.md +1 -1
- package/dist/global-skills/wrangler/SKILL.md +3 -3
- package/dist/global-skills/xcode-project-setup/SKILL.md +1 -0
- package/package.json +8 -8
|
@@ -10,6 +10,7 @@ Contents:
|
|
|
10
10
|
- [Extensions](#extensions): custom migration required to create extensions (Drizzle can't)
|
|
11
11
|
- [Schema](#schema): columns, generated `tsvector`, and the ANN index
|
|
12
12
|
- [BM25 Index](#bm25-index): created after the corpus is seeded
|
|
13
|
+
- [Generate and Store Embeddings](#generate-and-store-embeddings): call the AI Gateway and insert vectors with Drizzle
|
|
13
14
|
- [Query](#query): vector, BM25, and hybrid reads
|
|
14
15
|
- [Tune Per Query](#tune-per-query): per-query GUCs
|
|
15
16
|
|
|
@@ -22,18 +23,22 @@ Rules:
|
|
|
22
23
|
|
|
23
24
|
## Config
|
|
24
25
|
|
|
25
|
-
`drizzle-kit generate` and `migrate` read `drizzle.config.ts`. Point `dbCredentials.url` at the **direct (unpooled)** connection string:
|
|
26
|
+
`drizzle-kit generate` and `migrate` read `drizzle.config.ts`. If the connection string is in `.env`, install `dotenv` (`npm install dotenv`) and load it here. Point `dbCredentials.url` at the **direct (unpooled)** connection string:
|
|
26
27
|
|
|
27
28
|
```typescript
|
|
28
29
|
// drizzle.config.ts
|
|
30
|
+
import "dotenv/config";
|
|
29
31
|
import { defineConfig } from "drizzle-kit";
|
|
30
32
|
|
|
33
|
+
const databaseUrl = process.env.DATABASE_URL_UNPOOLED;
|
|
34
|
+
if (!databaseUrl) throw new Error("Missing DATABASE_URL_UNPOOLED");
|
|
35
|
+
|
|
31
36
|
export default defineConfig({
|
|
32
37
|
schema: "./src/schema.ts",
|
|
33
38
|
out: "./drizzle",
|
|
34
39
|
dialect: "postgresql",
|
|
35
40
|
// Direct (unpooled) URL. Neon exposes it as DATABASE_URL_UNPOOLED.
|
|
36
|
-
dbCredentials: { url:
|
|
41
|
+
dbCredentials: { url: databaseUrl },
|
|
37
42
|
});
|
|
38
43
|
```
|
|
39
44
|
|
|
@@ -57,7 +62,14 @@ The columns, the generated `tsvector`, and the `lakebase_ann` index all go in `s
|
|
|
57
62
|
|
|
58
63
|
```typescript
|
|
59
64
|
// src/schema.ts
|
|
60
|
-
import {
|
|
65
|
+
import {
|
|
66
|
+
pgTable,
|
|
67
|
+
bigint,
|
|
68
|
+
text,
|
|
69
|
+
vector,
|
|
70
|
+
index,
|
|
71
|
+
customType,
|
|
72
|
+
} from "drizzle-orm/pg-core";
|
|
61
73
|
import { sql } from "drizzle-orm";
|
|
62
74
|
|
|
63
75
|
const tsvector = customType<{ data: string }>({
|
|
@@ -69,10 +81,12 @@ const tsvector = customType<{ data: string }>({
|
|
|
69
81
|
export const documents = pgTable(
|
|
70
82
|
"documents",
|
|
71
83
|
{
|
|
72
|
-
id: bigint("id", { mode: "number" })
|
|
84
|
+
id: bigint("id", { mode: "number" })
|
|
85
|
+
.generatedByDefaultAsIdentity()
|
|
86
|
+
.primaryKey(),
|
|
73
87
|
title: text("title").notNull(),
|
|
74
88
|
body: text("body").notNull(),
|
|
75
|
-
embedding: vector("embedding", { dimensions:
|
|
89
|
+
embedding: vector("embedding", { dimensions: 1024 }),
|
|
76
90
|
bodyTsv: tsvector("body_tsv").generatedAlwaysAs(
|
|
77
91
|
sql`to_tsvector('english', "body")`,
|
|
78
92
|
),
|
|
@@ -86,7 +100,7 @@ export const documents = pgTable(
|
|
|
86
100
|
);
|
|
87
101
|
```
|
|
88
102
|
|
|
89
|
-
|
|
103
|
+
The 1024-dimensional column is an example; set it to the output dimension of your selected embedding model. Use the same model and dimensions for stored documents and queries. Postgres maintains `body_tsv`, so never write it from the app. Generate and apply the migration after the extensions migration above:
|
|
90
104
|
|
|
91
105
|
```bash
|
|
92
106
|
npx drizzle-kit generate --name=lakebase_search
|
|
@@ -106,6 +120,66 @@ npx drizzle-kit generate --custom --name=bm25_index
|
|
|
106
120
|
CREATE INDEX documents_body_bm25 ON documents USING lakebase_bm25 (body_tsv);
|
|
107
121
|
```
|
|
108
122
|
|
|
123
|
+
## Generate and Store Embeddings
|
|
124
|
+
|
|
125
|
+
Use the [Vercel AI SDK](https://ai-sdk.dev) (`ai`, 6+) with Neon's `@neon/ai-sdk-provider` 1.0.0+. Follow the [gateway setup](vector-search.md#prepare-embeddings), then install the packages with `npm install @neon/ai-sdk-provider ai dotenv`:
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
import "dotenv/config";
|
|
129
|
+
import { neon } from "@neon/ai-sdk-provider";
|
|
130
|
+
import { embed, embedMany } from "ai";
|
|
131
|
+
|
|
132
|
+
const embeddingModel = neon.embeddingModel("qwen3-embedding-0-6b");
|
|
133
|
+
const providerOptions = { neon: { dimensions: 1024 } };
|
|
134
|
+
// Qwen3 query prompt adapted for Neon documentation search; documents stay unprefixed.
|
|
135
|
+
const queryPrompt =
|
|
136
|
+
"Instruct: Given a question about Neon, retrieve documentation passages that answer the question\nQuery: ";
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Generate a document vector, then insert it with Drizzle:
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
const { embedding } = await embed({
|
|
143
|
+
model: embeddingModel,
|
|
144
|
+
value: body,
|
|
145
|
+
providerOptions,
|
|
146
|
+
});
|
|
147
|
+
await db.insert(documents).values({
|
|
148
|
+
title,
|
|
149
|
+
body,
|
|
150
|
+
embedding,
|
|
151
|
+
});
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
For bulk imports, skip blank rows and embed/insert one batch at a time:
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
const batchSize = 64;
|
|
158
|
+
const nonBlankRows = rows.filter((row) => row && row.body.trim().length > 0);
|
|
159
|
+
|
|
160
|
+
for (let offset = 0; offset < nonBlankRows.length; offset += batchSize) {
|
|
161
|
+
const batch = nonBlankRows.slice(offset, offset + batchSize);
|
|
162
|
+
const { embeddings: vectors } = await embedMany({
|
|
163
|
+
model: embeddingModel,
|
|
164
|
+
values: batch.map((row) => row.body),
|
|
165
|
+
providerOptions,
|
|
166
|
+
maxParallelCalls: 1,
|
|
167
|
+
});
|
|
168
|
+
await db.insert(documents).values(
|
|
169
|
+
batch.map((row, index) => {
|
|
170
|
+
const embedding = vectors[index];
|
|
171
|
+
if (!embedding)
|
|
172
|
+
throw new Error(`Missing embedding for batch row ${index}`);
|
|
173
|
+
return { ...row, embedding };
|
|
174
|
+
}),
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
`embedMany` splits embedding requests using the provider's per-request input limit. This loop also bounds database inserts and memory; tune `batchSize` for your workload. Chunk long documents before embedding. Earlier batches stay committed if a later batch fails.
|
|
180
|
+
|
|
181
|
+
Apply `queryPrompt` only to queries, keeping the original text for BM25. Use the same model and dimensions for documents and queries; re-embed documents when switching models.
|
|
182
|
+
|
|
109
183
|
## Query
|
|
110
184
|
|
|
111
185
|
Use the query builder with Drizzle's `cosineDistance` helper for vector search. It emits the `<=>` operator, so keep the index on `vector_cosine_ops`:
|
|
@@ -114,7 +188,11 @@ Use the query builder with Drizzle's `cosineDistance` helper for vector search.
|
|
|
114
188
|
import { cosineDistance } from "drizzle-orm";
|
|
115
189
|
import { documents } from "./schema";
|
|
116
190
|
|
|
117
|
-
|
|
191
|
+
const { embedding: queryEmbedding } = await embed({
|
|
192
|
+
model: embeddingModel,
|
|
193
|
+
value: `${queryPrompt}${queryText}`,
|
|
194
|
+
providerOptions,
|
|
195
|
+
});
|
|
118
196
|
const distance = cosineDistance(documents.embedding, queryEmbedding);
|
|
119
197
|
|
|
120
198
|
const rows = await db
|
|
@@ -130,17 +208,23 @@ BM25 has no Drizzle helper: `<@>` and `to_bm25query` require raw SQL. Reference
|
|
|
130
208
|
import { sql } from "drizzle-orm";
|
|
131
209
|
|
|
132
210
|
const rows = await db.execute(sql`
|
|
133
|
-
SELECT id, title,
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
211
|
+
SELECT id, title, score
|
|
212
|
+
FROM (
|
|
213
|
+
SELECT id, title,
|
|
214
|
+
body_tsv <@> to_bm25query(
|
|
215
|
+
to_tsvector('english', ${queryText}),
|
|
216
|
+
'documents_body_bm25'::regclass
|
|
217
|
+
) AS score
|
|
218
|
+
FROM documents
|
|
219
|
+
) AS keyword_scored
|
|
220
|
+
WHERE score < 0
|
|
139
221
|
ORDER BY score
|
|
140
222
|
LIMIT ${k}
|
|
141
223
|
`);
|
|
142
224
|
```
|
|
143
225
|
|
|
226
|
+
Filter `score < 0` before the candidate cutoff: scalar BM25 scoring returns zero for non-matches, which must not become keyword hits. Partial query-term matches remain eligible.
|
|
227
|
+
|
|
144
228
|
Run the [hybrid search](hybrid-search.md) RRF query the same way: raw SQL through `db.execute`.
|
|
145
229
|
|
|
146
230
|
## Tune Per Query
|
|
@@ -168,5 +252,6 @@ const rows = await db.transaction(async (tx) => {
|
|
|
168
252
|
|
|
169
253
|
Sources:
|
|
170
254
|
|
|
255
|
+
- [AI Gateway embeddings](https://neon.com/docs/ai-gateway/embeddings)
|
|
171
256
|
- [Get started with Lakebase Search](https://neon.com/docs/ai/lakebase-search-get-started)
|
|
172
|
-
- [Schema migration with Lakebase Postgres and Drizzle ORM](https://neon.com/docs/guides/drizzle-migrations)
|
|
257
|
+
- [Schema migration with Lakebase Postgres and Drizzle ORM](https://neon.com/docs/guides/drizzle-migrations)
|
|
@@ -5,7 +5,7 @@ Use `lakebase_vector` for approximate nearest-neighbor retrieval over embeddings
|
|
|
5
5
|
## Contents
|
|
6
6
|
|
|
7
7
|
- [Create the extension](#create-the-extension) — enable `lakebase_vector` and its `pgvector` dependency
|
|
8
|
-
- [Prepare embeddings](#prepare-embeddings) —
|
|
8
|
+
- [Prepare embeddings](#prepare-embeddings) — generate vectors with the AI Gateway and match the column dimension
|
|
9
9
|
- [Build the index](#build-the-index) — match the distance metric, operator class, and query operator
|
|
10
10
|
- [Tune the index](#tune-the-index) — configure index-build options and concurrent index management
|
|
11
11
|
- [Query](#query) — rank by vector distance or filter by a similarity radius
|
|
@@ -24,28 +24,111 @@ CREATE EXTENSION IF NOT EXISTS lakebase_vector CASCADE;
|
|
|
24
24
|
|
|
25
25
|
## Prepare Embeddings
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
Neon's [AI Gateway embeddings endpoint](https://neon.com/docs/ai-gateway/embeddings) is an OpenAI-compatible way to generate vectors for Lakebase Search. This table uses 1024 dimensions as an example; set the column dimension to the output of the model and any `dimensions` parameter you choose:
|
|
28
28
|
|
|
29
29
|
```sql
|
|
30
30
|
CREATE TABLE documents (
|
|
31
31
|
id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
|
32
32
|
title text NOT NULL,
|
|
33
33
|
body text NOT NULL,
|
|
34
|
-
embedding vector(
|
|
34
|
+
embedding vector(1024)
|
|
35
35
|
);
|
|
36
36
|
```
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
Enable the AI Gateway on the branch and get `NEON_AI_GATEWAY_TOKEN` and the bare-host `NEON_AI_GATEWAY_BASE_URL`:
|
|
39
|
+
|
|
40
|
+
- With `neon.ts`, declare `aiGateway: true`, run `neon deploy`, then run `neon env pull --file .env`.
|
|
41
|
+
- Without `neon.ts`, run `neon env pull --service ai-gateway --file .env` from a directory linked to the intended project and branch. This requests the gateway variables explicitly and skips other services.
|
|
42
|
+
- Alternatively, get both values from the Neon Console. Neon Functions receive them automatically.
|
|
43
|
+
|
|
44
|
+
### AI SDK (Recommended)
|
|
45
|
+
|
|
46
|
+
For TypeScript apps, use Neon's `@neon/ai-sdk-provider` 1.0.0+ with the [Vercel AI SDK](https://ai-sdk.dev) (`ai`, 6+) (`npm install @neon/ai-sdk-provider ai dotenv`). The provider reads `NEON_AI_GATEWAY_TOKEN` and `NEON_AI_GATEWAY_BASE_URL` directly and sends embedding requests to `/v1/embeddings`. Load `.env` in a standalone app; Neon Functions inject the variables automatically.
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
import "dotenv/config";
|
|
50
|
+
import { neon } from "@neon/ai-sdk-provider";
|
|
51
|
+
import { embed, embedMany } from "ai";
|
|
52
|
+
|
|
53
|
+
const embeddingModel = neon.embeddingModel("qwen3-embedding-0-6b");
|
|
54
|
+
const providerOptions = { neon: { dimensions: 1024 } };
|
|
55
|
+
// Qwen3 query prompt adapted for Neon documentation search; documents stay unprefixed.
|
|
56
|
+
const queryPrompt =
|
|
57
|
+
"Instruct: Given a question about Neon, retrieve documentation passages that answer the question\nQuery: ";
|
|
58
|
+
|
|
59
|
+
const { embeddings: documentEmbeddings } = await embedMany({
|
|
60
|
+
model: embeddingModel,
|
|
61
|
+
values: documentBodies, // non-empty document batch; store vectors by input index
|
|
62
|
+
providerOptions,
|
|
63
|
+
maxParallelCalls: 1,
|
|
64
|
+
});
|
|
65
|
+
const { embedding: queryEmbedding } = await embed({
|
|
66
|
+
model: embeddingModel,
|
|
67
|
+
value: `${queryPrompt}${queryText}`,
|
|
68
|
+
providerOptions,
|
|
69
|
+
}); // bind to $1 below
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Skip empty imports before calling `embedMany`. It returns vectors in input order and splits requests according to the provider's item limit. For large imports, also split document text within the endpoint's token limits and keep database writes bounded.
|
|
73
|
+
|
|
74
|
+
For Neon documentation search, the example adapts the task instruction in Qwen3's stored `query` prompt from [config_sentence_transformers.json](https://huggingface.co/Qwen/Qwen3-Embedding-0.6B/blob/main/config_sentence_transformers.json). Its `document` prompt is empty, so document text stays unprefixed. Neither SDK applies SentenceTransformers prompts automatically. Keep the original `queryText` for BM25, and use the selected model's own prompt conventions if you switch models.
|
|
75
|
+
|
|
76
|
+
### OpenAI SDK Alternative
|
|
77
|
+
|
|
78
|
+
For an existing OpenAI SDK integration, install `openai` and `dotenv` (`npm install openai dotenv`) and configure the client explicitly:
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
import "dotenv/config";
|
|
82
|
+
import OpenAI from "openai";
|
|
83
|
+
|
|
84
|
+
const apiKey = process.env.NEON_AI_GATEWAY_TOKEN;
|
|
85
|
+
const gatewayBaseUrl = process.env.NEON_AI_GATEWAY_BASE_URL;
|
|
86
|
+
if (!apiKey || !gatewayBaseUrl) {
|
|
87
|
+
throw new Error("Missing Neon AI Gateway credentials");
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const client = new OpenAI({
|
|
91
|
+
apiKey,
|
|
92
|
+
baseURL: `${gatewayBaseUrl}/v1`,
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
async function embedBatch(inputs: string[]): Promise<number[][]> {
|
|
96
|
+
if (inputs.length === 0) return [];
|
|
97
|
+
const response = await client.embeddings.create({
|
|
98
|
+
model: "qwen3-embedding-0-6b",
|
|
99
|
+
input: inputs,
|
|
100
|
+
dimensions: 1024,
|
|
101
|
+
encoding_format: "float",
|
|
102
|
+
});
|
|
103
|
+
const vectors: number[][] = [];
|
|
104
|
+
for (const item of response.data) vectors[item.index] = item.embedding;
|
|
105
|
+
return inputs.map((_, index) => {
|
|
106
|
+
const embedding = vectors[index];
|
|
107
|
+
if (!embedding) throw new Error(`Missing embedding for input ${index}`);
|
|
108
|
+
return embedding;
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const documentEmbeddings = await embedBatch(documentBodies);
|
|
113
|
+
const [queryEmbedding] = await embedBatch([`${queryPrompt}${queryText}`]);
|
|
114
|
+
if (!queryEmbedding) throw new Error("Missing query embedding");
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Use the same `queryPrompt` defined above, only for query inputs. Keep each batch within the endpoint's input and rate limits, and pair each vector with its source row using the response `index`. The [OpenAI JavaScript SDK v6](https://github.com/openai/openai-node/blob/v6.0.0/src/resources/embeddings.ts) defaults to requesting base64 and decodes the response as base64. If a compatible endpoint returns float arrays instead, that decoder can silently turn them into zeros. Pass `encoding_format: "float"` explicitly for the AI Gateway; the SDK then returns the float arrays directly. Base64 can reduce response size when an endpoint actually returns base64, but do not assume that format for this gateway.
|
|
118
|
+
|
|
119
|
+
Use the same model and output dimensions for stored documents and queries. Follow the model's input conventions: some models recommend a query instruction while document text remains unprefixed. If the model supports shorter vectors, compare retrieval quality, storage, and latency before changing dimensions; set the column and both embedding requests to the chosen size. Switching models requires re-embedding the corpus; changing dimensions also requires migrating the vector column and index. Generate embeddings in the application, outside SQL, unless the architecture already provides an in-database embedding function. Other embedding providers are also valid when their output matches the schema and index.
|
|
39
120
|
|
|
40
121
|
## Build the Index
|
|
41
122
|
|
|
42
123
|
Choose the operator class and query operator as a matched pair:
|
|
43
124
|
|
|
44
|
-
| Metric
|
|
45
|
-
|
|
|
46
|
-
| Cosine
|
|
47
|
-
| L2 / Euclidean | Absolute distance matters; vectors do not need normalization | `vector_l2_ops`
|
|
48
|
-
| Inner product
|
|
125
|
+
| Metric | Common use | Operator class | Distance operator |
|
|
126
|
+
| -------------- | ------------------------------------------------------------ | ------------------- | ----------------- |
|
|
127
|
+
| Cosine | Most text embeddings | `vector_cosine_ops` | `<=>` |
|
|
128
|
+
| L2 / Euclidean | Absolute distance matters; vectors do not need normalization | `vector_l2_ops` | `<->` |
|
|
129
|
+
| Inner product | Unit-normalized vectors; matches cosine for unit vectors | `vector_ip_ops` | `<#>` |
|
|
130
|
+
|
|
131
|
+
Choose the metric recommended for the selected model, and keep its index operator class and query operator aligned. The [Qwen3 Embedding model card](https://huggingface.co/Qwen/Qwen3-Embedding-0.6B) demonstrates cosine similarity and normalized dot product for retrieval. For unit-normalized vectors, cosine, inner product, and L2 have the same exact ranking; approximate indexes can still differ in recall and latency, so benchmark on your corpus.
|
|
49
132
|
|
|
50
133
|
```sql
|
|
51
134
|
CREATE INDEX documents_embedding_ann ON documents
|
|
@@ -79,7 +162,7 @@ For a large table, use `CREATE INDEX CONCURRENTLY` to avoid locking out writes w
|
|
|
79
162
|
|
|
80
163
|
## Query
|
|
81
164
|
|
|
82
|
-
Generate the query embedding with the same model and
|
|
165
|
+
Generate the query embedding with the same model and dimensions used for stored documents, following the model's query-input conventions (using the Qwen3 query prompt shown above), then bind it as a parameter:
|
|
83
166
|
|
|
84
167
|
```sql
|
|
85
168
|
SELECT id, title, embedding <=> $1::vector AS distance
|
|
@@ -4,7 +4,7 @@ description: Use when working with the Resend email API — sending transactiona
|
|
|
4
4
|
license: MIT
|
|
5
5
|
metadata:
|
|
6
6
|
author: resend
|
|
7
|
-
version: "3.
|
|
7
|
+
version: "3.14.0"
|
|
8
8
|
homepage: https://resend.com/agent-skills
|
|
9
9
|
source: https://github.com/resend/resend-skills
|
|
10
10
|
openclaw:
|
|
@@ -178,22 +178,11 @@ export async function POST(req: Request) {
|
|
|
178
178
|
|
|
179
179
|
## SDK Version Requirements
|
|
180
180
|
|
|
181
|
-
Always install the latest SDK version.
|
|
181
|
+
Always install the latest SDK version. Older SDKs may be missing `webhooks.verify()`, `emails.receiving.get()`, or `domains.claims.*`.
|
|
182
182
|
|
|
183
|
-
|
|
184
|
-
|----------|---------|-------------|---------|
|
|
185
|
-
| Node.js | `resend` | >= 6.14.0 | `npm install resend` |
|
|
186
|
-
| Python | `resend` | >= 2.34.0 | `pip install resend` |
|
|
187
|
-
| Go | `resend-go/v3` | >= 3.11.0 | `go get github.com/resend/resend-go/v3` |
|
|
188
|
-
| Ruby | `resend` | >= 1.6.0 | `gem install resend` |
|
|
189
|
-
| PHP | `resend/resend-php` | >= 1.1.0 | `composer require resend/resend-php` |
|
|
190
|
-
| Rust | `resend-rs` | >= 0.26.1 | `cargo add resend-rs` |
|
|
191
|
-
| Java | `resend-java` | >= 4.16.0 | See [installation.md](references/installation.md) |
|
|
192
|
-
| .NET | `Resend` | >= 0.2.1 | `dotnet add package Resend` |
|
|
183
|
+
> **If the project already has a Resend SDK installed**, check its version against the minimum versions in [installation.md](references/installation.md) and upgrade if it's below.
|
|
193
184
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
See [installation.md](references/installation.md) for full installation commands, language detection, and cURL fallback.
|
|
185
|
+
See [installation.md](references/installation.md) for the minimum version per language, full installation commands, language detection, and cURL fallback.
|
|
197
186
|
|
|
198
187
|
## Common Setup
|
|
199
188
|
|
|
@@ -108,7 +108,7 @@ Claiming takes over a domain **another Resend account has already verified**. Th
|
|
|
108
108
|
Claim → Add TXT proof to DNS → Verify claim → (completed) → Update DKIM in DNS → Verify domain → Send
|
|
109
109
|
```
|
|
110
110
|
|
|
111
|
-
Claim methods are available in the **Node.js** (`resend >= 6.14.0`), **Python** (`resend >= 2.34.0`), **Ruby** (`resend >= 1.6.0`), **Go** (`resend-go/
|
|
111
|
+
Claim methods are available in the **Node.js** (`resend >= 6.14.0`), **Python** (`resend >= 2.34.0`), **Ruby** (`resend >= 1.6.0`), **Go** (`resend-go/v4 >= 4.0.0`), **Rust** (`resend-rs >= 0.26.1`), and **Java** (`resend-java >= 4.16.0`) SDKs, plus the **CLI** (`resend domains claim`) and REST API. Not yet in the PHP or .NET SDKs.
|
|
112
112
|
|
|
113
113
|
| Operation | Method | Notes |
|
|
114
114
|
|-----------|--------|-------|
|
|
@@ -10,7 +10,7 @@ These are the minimum versions required for full functionality (sending, receivi
|
|
|
10
10
|
|----------|---------|-------------|---------|
|
|
11
11
|
| Node.js | `resend` | >= 6.14.0 | `npm install resend` |
|
|
12
12
|
| Python | `resend` | >= 2.34.0 | `pip install resend` |
|
|
13
|
-
| Go | `resend-go/
|
|
13
|
+
| Go | `resend-go/v4` | >= 4.0.0 | `go get github.com/resend/resend-go/v4` |
|
|
14
14
|
| Ruby | `resend` | >= 1.6.0 | `gem install resend` |
|
|
15
15
|
| PHP | `resend/resend-php` | >= 1.1.0 | `composer require resend/resend-php` |
|
|
16
16
|
| Rust | `resend-rs` | >= 0.26.1 | `cargo add resend-rs` |
|
|
@@ -27,7 +27,7 @@ Check for these files to determine the project's language/framework:
|
|
|
27
27
|
|------|----------|-----|
|
|
28
28
|
| `package.json` | Node.js/TypeScript | resend |
|
|
29
29
|
| `requirements.txt` or `pyproject.toml` | Python | resend |
|
|
30
|
-
| `go.mod` | Go | resend-go/
|
|
30
|
+
| `go.mod` | Go | resend-go/v4 |
|
|
31
31
|
| `Gemfile` | Ruby | resend |
|
|
32
32
|
| `composer.json` | PHP | resend/resend-php |
|
|
33
33
|
| `Cargo.toml` | Rust | resend-rs |
|
|
@@ -59,7 +59,7 @@ pip install resend
|
|
|
59
59
|
### Go
|
|
60
60
|
|
|
61
61
|
```bash
|
|
62
|
-
go get github.com/resend/resend-go/
|
|
62
|
+
go get github.com/resend/resend-go/v4
|
|
63
63
|
```
|
|
64
64
|
|
|
65
65
|
### Ruby
|
|
@@ -86,7 +86,7 @@ result = resend.Batch.send(emails, idempotency_key=f"batch-orders/{batch_id}")
|
|
|
86
86
|
Other SDKs use the `Idempotency-Key` header:
|
|
87
87
|
|
|
88
88
|
```go
|
|
89
|
-
import "github.com/resend/resend-go/
|
|
89
|
+
import "github.com/resend/resend-go/v4"
|
|
90
90
|
|
|
91
91
|
client := resend.NewClient(os.Getenv("RESEND_API_KEY"))
|
|
92
92
|
|
|
@@ -223,7 +223,7 @@ except resend.exceptions.ResendError as e:
|
|
|
223
223
|
```go
|
|
224
224
|
import (
|
|
225
225
|
"fmt"
|
|
226
|
-
"github.com/resend/resend-go/
|
|
226
|
+
"github.com/resend/resend-go/v4"
|
|
227
227
|
)
|
|
228
228
|
|
|
229
229
|
client := resend.NewClient(os.Getenv("RESEND_API_KEY"))
|
|
@@ -359,7 +359,7 @@ result = send_email_with_retry(
|
|
|
359
359
|
import (
|
|
360
360
|
"fmt"
|
|
361
361
|
"time"
|
|
362
|
-
"github.com/resend/resend-go/
|
|
362
|
+
"github.com/resend/resend-go/v4"
|
|
363
363
|
)
|
|
364
364
|
|
|
365
365
|
func sendEmailWithRetry(client *resend.Client, params *resend.SendEmailRequest, maxRetries int) (*resend.SendEmailResponse, error) {
|
|
@@ -58,7 +58,7 @@ email = resend.Emails.send({
|
|
|
58
58
|
Other SDKs use the `Idempotency-Key` header:
|
|
59
59
|
|
|
60
60
|
```go
|
|
61
|
-
import "github.com/resend/resend-go/
|
|
61
|
+
import "github.com/resend/resend-go/v4"
|
|
62
62
|
|
|
63
63
|
client := resend.NewClient(os.Getenv("RESEND_API_KEY"))
|
|
64
64
|
|
|
@@ -172,7 +172,7 @@ except resend.exceptions.ResendError as e:
|
|
|
172
172
|
```go
|
|
173
173
|
import (
|
|
174
174
|
"fmt"
|
|
175
|
-
"github.com/resend/resend-go/
|
|
175
|
+
"github.com/resend/resend-go/v4"
|
|
176
176
|
)
|
|
177
177
|
|
|
178
178
|
client := resend.NewClient(os.Getenv("RESEND_API_KEY"))
|
|
@@ -293,7 +293,7 @@ result = send_email_with_retry(
|
|
|
293
293
|
import (
|
|
294
294
|
"fmt"
|
|
295
295
|
"time"
|
|
296
|
-
"github.com/resend/resend-go/
|
|
296
|
+
"github.com/resend/resend-go/v4"
|
|
297
297
|
)
|
|
298
298
|
|
|
299
299
|
func sendEmailWithRetry(client *resend.Client, params *resend.SendEmailRequest, maxRetries int) (*resend.SendEmailResponse, error) {
|
|
@@ -25,13 +25,13 @@ Latest SDK versions:
|
|
|
25
25
|
|
|
26
26
|
| Language | Latest version |
|
|
27
27
|
| --- | --- |
|
|
28
|
-
| Ruby |
|
|
29
|
-
| Python |
|
|
30
|
-
| Node.js |
|
|
31
|
-
| PHP |
|
|
32
|
-
| Java |
|
|
33
|
-
| Go |
|
|
34
|
-
| .NET |
|
|
28
|
+
| Ruby | 20.0.0 |
|
|
29
|
+
| Python | 16.0.0 |
|
|
30
|
+
| Node.js | 23.0.0 |
|
|
31
|
+
| PHP | 22.0.0 |
|
|
32
|
+
| Java | 34.0.0 |
|
|
33
|
+
| Go | 87.0.0 |
|
|
34
|
+
| .NET | 53.0.0 |
|
|
35
35
|
|
|
36
36
|
If you don’t have a Stripe account, you can use the CLI to generate a sandbox test account with no registration required. Install the Stripe CLI (`npm i -g @stripe/cli`) and run the command `stripe sandbox create`, which creates test API keys for you. When you recommend this, explain that [a sandbox is a Stripe test environment](https://docs.stripe.com/sandboxes.md).
|
|
37
37
|
|
|
@@ -45,27 +45,27 @@ API key default: Always recommend a [restricted API key (RAK)](https://docs.stri
|
|
|
45
45
|
|
|
46
46
|
| Building… | Recommended API | Details |
|
|
47
47
|
| --- | --- | --- |
|
|
48
|
-
| One-time payments | Checkout Sessions |
|
|
49
|
-
| Custom payment form with embedded UI | Checkout Sessions + Payment Element |
|
|
50
|
-
| Saving a payment method for later | Setup Intents |
|
|
51
|
-
| Connect platform or marketplace | Accounts v2 (`/v2/core/accounts`) |
|
|
52
|
-
| Usage-based billing (new integration) | Metronome |
|
|
53
|
-
| Subscriptions or recurring billing | Billing APIs + Checkout Sessions |
|
|
54
|
-
| Sales tax, VAT, or GST compliance | Stripe Tax + Registrations API |
|
|
55
|
-
| Embedded financial accounts / banking | v2 Financial Accounts |
|
|
56
|
-
| Security (key management, RAKs, webhooks, OAuth, 2FA, Connect liability) | See security reference |
|
|
48
|
+
| One-time payments | Checkout Sessions | <references/payments.md> |
|
|
49
|
+
| Custom payment form with embedded UI | Checkout Sessions + Payment Element | <references/payments.md> |
|
|
50
|
+
| Saving a payment method for later | Setup Intents | <references/payments.md> |
|
|
51
|
+
| Connect platform or marketplace | Accounts v2 (`/v2/core/accounts`) | <references/connect.md> |
|
|
52
|
+
| Usage-based billing (new integration) | Metronome | <references/billing.md> |
|
|
53
|
+
| Subscriptions or recurring billing | Billing APIs + Checkout Sessions | <references/billing.md> |
|
|
54
|
+
| Sales tax, VAT, or GST compliance | Stripe Tax + Registrations API | <references/tax.md> |
|
|
55
|
+
| Embedded financial accounts / banking | v2 Financial Accounts | <references/treasury.md> |
|
|
56
|
+
| Security (key management, RAKs, webhooks, OAuth, 2FA, Connect liability) | See security reference | <references/security.md> |
|
|
57
57
|
|
|
58
58
|
Read the relevant reference file before answering any integration question or writing code.
|
|
59
59
|
|
|
60
60
|
## Critical rules
|
|
61
61
|
|
|
62
|
-
- *Before enabling `automatic_tax: { enabled: true }`* (or calculating tax for a custom PaymentIntent), read the [tax reference](
|
|
62
|
+
- *Before enabling `automatic_tax: { enabled: true }`* (or calculating tax for a custom PaymentIntent), read the [tax reference](references/tax.md) and confirm the user has an active registration. Without one, Stripe calculates and collects no tax while the user believes tax is on (the most common Stripe Tax mistake).
|
|
63
63
|
|
|
64
64
|
- *Never include `payment_method_types` in any Stripe API call*, with one exception: Terminal (in-person payments) integrations must pass `payment_method_types: ['card_present']` on the PaymentIntent. For all other integrations, omit this parameter entirely to enable dynamic payment methods, which enables you to configure payment method settings from the Dashboard and dynamically display the most relevant eligible payment methods to each customer to maximize conversion. To customize which payment methods you accept, use [payment_method_configurations](https://docs.stripe.com/payments/payment-method-configurations.md) or `excluded_payment_method_types` instead of `payment_method_types`.
|
|
65
65
|
|
|
66
66
|
- When a PaymentIntent or SetupIntent integration requires an explicit allowlist, use `allowed_payment_method_types` instead of `payment_method_types`.
|
|
67
67
|
|
|
68
|
-
- *Never present webhooks as optional.* We recommend webhooks for every payment integration and they’re required for subscriptions and asynchronous payment methods. Fulfillment belongs in a handler for both `checkout.session.completed` and `checkout.session.async_payment_succeeded` (gated on `payment_status`), not the success page. See
|
|
68
|
+
- *Never present webhooks as optional.* We recommend webhooks for every payment integration and they’re required for subscriptions and asynchronous payment methods. Fulfillment belongs in a handler for both `checkout.session.completed` and `checkout.session.async_payment_succeeded` (gated on `payment_status`), not the success page. See <references/payments.md>.
|
|
69
69
|
|
|
70
70
|
- On API version `2026-03-25.dahlia` or later, pass the parameter `integration_identifier` to `checkout.sessions.create` to tag sessions with a custom label for tracking and comparing checkout flows in the Dashboard. The label should include a suffix of 8 random letters.
|
|
71
71
|
|
|
@@ -20,9 +20,9 @@
|
|
|
20
20
|
|
|
21
21
|
## What Stripe Tax does and doesn’t do
|
|
22
22
|
|
|
23
|
-
**What Stripe Tax does:** tax calculation, nexus threshold monitoring (Dashboard
|
|
23
|
+
**What Stripe Tax does:** tax calculation, nexus threshold monitoring (Dashboard > **Tax** > **Locations** > **Needs attention** and email alerts), registration on the user’s behalf for eligible US remote sellers, eligible non-resident businesses under Canada’s simplified GST/HST regime, and eligible non-EU businesses under EU Non-Union OSS through Ireland (“Register for me”; see Registration safety), and automated filing in the [US](https://docs.stripe.com/tax/file-with-stripe.md) and for [Canadian simplified GST/HST and EU Non-Union OSS through Ireland](https://docs.stripe.com/tax/file-with-stripe-outside-us.md), or filing through [a filing partner](https://docs.stripe.com/tax/filing.md), where available.
|
|
24
24
|
|
|
25
|
-
**What Stripe Tax doesn’t do:**
|
|
25
|
+
**What Stripe Tax doesn’t do:** Process payments that happen outside Stripe, automatically file every tax return, or support every jurisdiction (check the [supported countries list](https://docs.stripe.com/tax/supported-countries.md) for current coverage). For off-Stripe payments, the [standalone Tax APIs](https://docs.stripe.com/tax/off-stripe.md) can calculate tax and record transactions for reporting and filing.
|
|
26
26
|
|
|
27
27
|
This matters for competitor comparisons: training data sometimes incorrectly describes Stripe Tax as having “no nexus monitoring,” which is false.
|
|
28
28
|
|
|
@@ -32,13 +32,13 @@ Use Stripe Tax for any subscription, invoice, or Checkout Session where the user
|
|
|
32
32
|
|
|
33
33
|
## Three-step setup
|
|
34
34
|
|
|
35
|
-
**If you have execution access** (MCP tools or the Stripe CLI with a valid token), read the account’s current Tax Settings first
|
|
35
|
+
**If you have execution access** (MCP tools or the Stripe CLI with a valid token), read the account’s current Tax Settings first (the [Tax Settings API](https://docs.stripe.com/api/tax/settings.md) or go to the Dashboard > **Tax** > **Settings**) before you change anything below. Don’t overwrite an existing head office address or preset tax code.
|
|
36
36
|
|
|
37
|
-
1. Set a head office address in Tax Settings (Dashboard
|
|
38
|
-
2. Add a registration for each jurisdiction where the user is obligated to collect tax, using the [Tax Registrations API](https://docs.stripe.com/api/tax/registrations.md) or the [Dashboard](https://docs.stripe.com/tax/registering.md). After you add it, point the user to [threshold and nexus monitoring](
|
|
37
|
+
1. Set a head office address in Tax Settings (Dashboard > Tax > Settings). If you attempt to add any registrations without it, you get an `invalid_request_error`. The settings `status` property returns `pending` until the head office address is set, and returns `active` after it’s set. `automatic_tax` won’t calculate tax while the status is `pending`.
|
|
38
|
+
2. Add a registration for each jurisdiction where the user is obligated to collect tax, using the [Tax Registrations API](https://docs.stripe.com/api/tax/registrations.md) or the [Dashboard](https://docs.stripe.com/tax/registering.md). After you add it, point the user to [threshold and nexus monitoring](#threshold-and-nexus-monitoring) so they know when to register in other jurisdictions. Don’t limit the conversation to the jurisdiction you just registered.
|
|
39
39
|
3. Pass `automatic_tax: { enabled: true }` on the [Subscription](https://docs.stripe.com/api/subscriptions.md), [Invoice](https://docs.stripe.com/api/invoices.md), or [Checkout Session](https://docs.stripe.com/api/checkout/sessions.md) object.
|
|
40
40
|
|
|
41
|
-
**If you have execution access** (MCP tools or the Stripe CLI with a valid token), don’t hand the user a checklist item that says “run a test calculation.” Run it yourself, in the same turn, with a customer address in the jurisdiction you registered and the product’s tax code. See [Verify before you trust automatic tax](
|
|
41
|
+
**If you have execution access** (MCP tools or the Stripe CLI with a valid token), don’t hand the user a checklist item that says “run a test calculation.” Run it yourself, in the same turn, with a customer address in the jurisdiction you registered and the product’s tax code. See [Verify before you trust automatic tax](#verify-before-you-trust-automatic-tax).
|
|
42
42
|
|
|
43
43
|
An *active registration* is a jurisdiction you’ve added to Stripe that shows as *Collecting*. It’s per-jurisdiction, and not the same as having a Stripe account.
|
|
44
44
|
|
|
@@ -54,7 +54,7 @@ After enabling `automatic_tax`, don’t assume the setup is complete: tax is onl
|
|
|
54
54
|
|
|
55
55
|
**If you have execution access** (MCP tools or the Stripe CLI with a valid token), run a test [Tax Calculation](https://docs.stripe.com/api/tax/calculations.md) with a customer address in the target jurisdiction and the product’s tax code. Check `tax_breakdown[].taxability_reason`, not the tax amount.
|
|
56
56
|
|
|
57
|
-
- `not_collecting` means the setup is broken — a registration or tax code gap. Don’t tell the user their setup works. See [Diagnose zero tax](
|
|
57
|
+
- `not_collecting` means the setup is broken — a registration or tax code gap. Don’t tell the user their setup works. See [Diagnose zero tax](#diagnose-zero-tax).
|
|
58
58
|
- Any other reason means the calculation worked, including when the tax is zero. Zero is *correct* for an exempt tax code or an exempt customer. Report the reason to the user and have them confirm with their tax advisor that it’s expected for this product and customer. Never swap in a different tax code to produce tax.
|
|
59
59
|
- Run it in the same turn. Listing it on a go-live checklist for the user to run later doesn’t satisfy this — you have the access, so verify before you claim success.
|
|
60
60
|
- If you only have read or advisory access, don’t claim it’s verified. Point the user to [Testing Stripe Tax](https://docs.stripe.com/tax/testing.md) to run the check themselves in a sandbox.
|
|
@@ -84,7 +84,7 @@ For all other `taxability_reason` values — `reverse_charge`, `customer_exempt`
|
|
|
84
84
|
|
|
85
85
|
**Remediation order when `automatic_tax` collects zero tax:**
|
|
86
86
|
|
|
87
|
-
1. Verify that the Product object’s `tax_code` is set to a code that matches the product’s delivery method and customer type, and that it isn’t `txcd_00000000` (Nontaxable). Use [Choosing a product tax code](
|
|
87
|
+
1. Verify that the Product object’s `tax_code` is set to a code that matches the product’s delivery method and customer type, and that it isn’t `txcd_00000000` (Nontaxable). Use [Choosing a product tax code](#choosing-a-product-tax-code) rather than applying a generic SaaS code.
|
|
88
88
|
2. Add a tax registration for the customer’s jurisdiction.
|
|
89
89
|
3. Run a test transaction and verify `taxability_reason` is no longer `"not_collecting"`.
|
|
90
90
|
|
|
@@ -119,7 +119,9 @@ As soon as you know the liable entity:
|
|
|
119
119
|
|
|
120
120
|
## Threshold and nexus monitoring
|
|
121
121
|
|
|
122
|
-
|
|
122
|
+
**If you have execution access** (MCP tools or the Stripe CLI with a valid token), use the GetTaxThresholdMonitoring MCP dynamic tool or the `get_tax_threshold_monitoring` CLI tool to retrieve the account’s current threshold monitoring results. Report the relevant jurisdictions and when the results were computed, if that timestamp is available. If a jurisdiction is absent or its status is undetermined, say so rather than inferring an obligation.
|
|
123
|
+
|
|
124
|
+
The [threshold monitoring](https://docs.stripe.com/tax/monitoring.md) tool highlights *potential* registration obligations in the Dashboard > **Tax** > **Locations** > **Needs attention**. Stripe sends email and Dashboard alerts. The public guide documents those notification methods, so don’t promise a threshold-alert API or webhook. Monitoring doesn’t cover physical-presence obligations. Present it as information and tell the user to discuss it with their tax advisor. It’s up to the user to confirm whether registration is required. Don’t tell them they must register, and don’t recommend a universal percentage of a threshold as the point to register.
|
|
123
125
|
|
|
124
126
|
Threshold monitoring only processes live-mode transactions, not sandbox payments. Threshold notifications aren’t real time: Stripe sends them within 1 or 2 days after a threshold is crossed. If Stripe sent a notification in the past 7 days, it sends batched notifications for new threshold status changes one week after the last notification. Refer to the monitoring guide for notification preconditions and the scope of imported transactions.
|
|
125
127
|
|
|
@@ -133,15 +135,25 @@ Guide, don’t advise. Never tell a user where they must register or whether the
|
|
|
133
135
|
|
|
134
136
|
**How to register.** Present the paths that fit the user and let them (with their tax advisor) choose. Don’t pick for them.
|
|
135
137
|
|
|
136
|
-
- **Register themselves, then record it in Stripe**: the user registers directly with the relevant tax authority and obtains their registration number. Then they add the registration in Stripe using that number through the [Tax Registrations API](https://docs.stripe.com/api/tax/registrations.md) or Dashboard
|
|
137
|
-
|
|
138
|
-
- **
|
|
138
|
+
- **Register themselves, then record it in Stripe**: the user registers directly with the relevant tax authority and obtains their registration number. Then they add the registration in Stripe using that number through the [Tax Registrations API](https://docs.stripe.com/api/tax/registrations.md) or the Dashboard > **Tax** > **Locations** > **Add registration**. See [Register for tax](https://docs.stripe.com/tax/registering.md).
|
|
139
|
+
|
|
140
|
+
- **Ask Stripe to register**: With Tax Complete and **Register for me**, Stripe submits the registration to the tax authority and adds the completed registration to the Dashboard, so the user doesn’t record it separately. Stripe supports the following:
|
|
141
|
+
|
|
142
|
+
- Eligible US remote sellers
|
|
143
|
+
|
|
144
|
+
- Canada’s simplified GST/HST registration for eligible non-resident businesses selling digital products or services to Canadian customers
|
|
145
|
+
|
|
146
|
+
- EU Non-Union OSS registration through Ireland for eligible businesses established outside the EU with no EU fixed establishment that sell services to individual customers in the EU
|
|
147
|
+
|
|
148
|
+
Check the [US eligibility requirements](https://docs.stripe.com/tax/use-stripe-to-register/united-states.md#eligibility) or [eligibility and signup steps outside the US](https://docs.stripe.com/tax/use-stripe-to-register/outside-united-states.md) before recommending this path. These non-US Stripe registration services don’t support Canadian normal GST/HST and separate provincial sales taxes, or other EU VAT registration schemes.
|
|
149
|
+
|
|
150
|
+
- **Register with Taxually where available**: Taxually can help with registrations outside the United States, including registration types not covered by the Stripe registration service. Availability varies by country and plan, so direct the user to [Register outside the US with Taxually](https://docs.stripe.com/tax/use-taxually-to-register.md) for current coverage.
|
|
139
151
|
|
|
140
|
-
**Reporting and filing
|
|
152
|
+
**Reporting and filing**: Collecting with Stripe Tax doesn’t file a return by itself, and **Register for me** doesn’t enroll the user in filing. With Tax Complete, users can set up automated filing with Stripe for [US sales tax](https://docs.stripe.com/tax/file-with-stripe.md), [Canada’s simplified GST/HST and EU Non-Union OSS through Ireland](https://docs.stripe.com/tax/file-with-stripe-outside-us.md). US TaxJar filing requires a US-based bank account. Users can also use [a filing partner](https://docs.stripe.com/tax/filing.md) where available. Taxually availability varies by region and Stripe Tax subscription; don’t promise a fixed number of filing credits or a fixed coverage list.
|
|
141
153
|
|
|
142
154
|
## Testing considerations
|
|
143
155
|
|
|
144
|
-
- Tax registrations in a sandbox are scoped to that sandbox. They don’t appear in live mode and must be re-created. Point the user to Dashboard
|
|
156
|
+
- Tax registrations in a sandbox are scoped to that sandbox. They don’t appear in live mode and must be re-created. Point the user to the Dashboard > **Tax** > **Locations** in live mode to add registrations before processing real payments.
|
|
145
157
|
- Tax Settings are separate for sandboxes. Configure Tax Settings in each sandbox you use, and verify live-mode settings separately before processing real payments.
|
|
146
158
|
- Add live-mode registrations before the first real transaction. If a transaction occurs with no active tax registration, `automatic_tax` silently collects 0 tax, with no error or warning.
|
|
147
159
|
- Sandbox transactions have no effect on threshold monitoring. Don’t infer live-mode threshold activity from sandbox activity.
|