Troubleshooting
My query returns no rows
Section titled “My query returns no rows”Requests with no signed-in user read nothing unless the table has a public rule for reading. For user data, send the user’s access token and give the table a claim row rule.
- List the table’s rules with
GET /rls/policies/{projectId}, using a console access token. - A claim rule returns only rows whose column equals a claim in the caller’s access token, such as
author_idequal tosub. Check that the rows hold the signed-in user’s id in that column, and that the request carries the user’s token (accessTokenincreateVaifClient, or anAuthorization: Bearerheader). An expired token, or one from another project, counts as no token. - Compare with the console’s SQL editor, which row rules do not limit.
401 on every request
Section titled “401 on every request”- The key starts with
vk_pub_,vk_srv_orvaif_, and is still listed on Security > Credentials. - The project ID is the one the key belongs to. A key for another project is refused.
- The key was created in the environment you are calling. Keys belong to one environment.
baseUrlishttps://api.vaif.studio.- Read the
codein the response, such asTOKEN_EXPIREDorINVALID_API_KEY. See Error codes.
import { createVaifClient } from "@vaifstudio/client";
const vaif = createVaifClient({ baseUrl: "https://api.vaif.studio", projectId: process.env.VAIF_PROJECT_ID, apiKey: process.env.VAIF_PUBLISHABLE_KEY,});Management calls return 403 SCOPE_INSUFFICIENT
Section titled “Management calls return 403 SCOPE_INSUFFICIENT”Schema changes, row rules, billing and project administration need a console access token, not an API key. See Access tokens.
Realtime doesn’t deliver events
Section titled “Realtime doesn’t deliver events”- Realtime must be on for the table: turn it on under Realtime > Tables, or on the table’s Realtime tab in the Table Editor.
- The client needs an API key, and the user’s access token when row rules apply.
- Watch the subscription status:
CHANNEL_ERRORmeans the server refused it, for example for an unknown table or column, or a filter realtime cannot apply.
See Realtime.
429 Too Many Requests
Section titled “429 Too Many Requests”Wait for the number of seconds in Retry-After, and avoid polling: subscribe to changes with realtime instead.
import { VaifRateLimitError } from "@vaifstudio/client";
async function withRetry<T>(fn: () => Promise<T>, attempts = 3): Promise<T> { for (let i = 0; ; i++) { try { return await fn(); } catch (error) { if (!(error instanceof VaifRateLimitError) || i >= attempts - 1) throw error; const seconds = error.retryAfter ?? 2 ** i; await new Promise((resolve) => setTimeout(resolve, seconds * 1000)); } }}
const { data: posts } = await withRetry(() => vaif.from("posts").limit(20).list());console.log(posts.length);The SDK already retries reads (GET) on network errors and on 500, 502, 503 and 504. A read that gets a 429 is retried once, never sooner than its Retry-After. The SDK never replays a write on its own.
Slow queries
Section titled “Slow queries”Add indexes on the columns you filter and sort by, fetch only the columns you need, and page through results. Add an index on the Schema page, or in a migration file applied with vaif db push:
CREATE INDEX idx_posts_created ON posts (created_at);const page = 2;const { data } = await vaif .from("posts") .select("id", "title", "created_at") .orderBy("created_at", "desc") .limit(20) .offset(page * 20) .list();console.log(data);A list returns 50 rows unless you set limit, and at most 1000 rows per request; a higher limit is refused with LIMIT_TOO_HIGH.
A storage upload fails
Section titled “A storage upload fails”Check that the bucket exists, and that its policies let the current user upload to that path, with that content type and size. Uploads also count toward your plan’s storage limit; see Plans.
Still stuck?
Section titled “Still stuck?”Email support@vaif.studio with the requestId from the error response.