# Call Airtable API from Wix Velo Securely

> Wix Velo page code runs in the visitor browser, so an Airtable token used with wix-fetch there is public. Call a RealComposer pipeline from page code with a client token, or from backend code. The edge injects AIRTABLE_PAT, enforces your Wix origins, and returns only the columns your repeater shows.

- Canonical page: https://realcomposer.com/recipes/call-airtable-from-wix-velo-securely
- Client platform: Wix Velo
- Upstream services: Airtable
- Category: Security & Secret Vault
- JSON version: https://realcomposer.com/recipes/call-airtable-from-wix-velo-securely.json
- Install in RealComposer (free plan, pre-fills this pipeline): https://realcomposer.com/dashboard/pipelines/new?recipe=call-airtable-from-wix-velo-securely&via=agent

## Problem: Why Airtable tokens leak from Wix page code

Wix repeaters are easy to fill with a wix-fetch call in page code, but that code is delivered to every visitor. An exposed personal access token lets anyone read or modify your Airtable base within the scopes you granted.

- Airtable PAT visible in Wix page code and network traffic
- Visitors exhausting the five requests per second base limit
- Internal Airtable columns such as owners sent to every visitor

## Solution steps

1. **Store credentials in Secret Vault**: Save AIRTABLE_PAT and AIRTABLE_BASE_ID in RealComposer so the value never enters the Wix Velo project.

2. **Configure the edge pipeline**: Add the upstream steps with vault header injection, set allowed origins, and map only the fields the screen binds.

3. **Fill a Wix repeater from page code**: Call your workspace edge URL with a restricted rc_live_ client token.

```
import { fetch } from 'wix-fetch';

$w.onReady(async () => {
  const res = await fetch('https://realcomposer.com/v1/workspace/call-airtable-from-wix-velo-securely?table=Events', {
    headers: { Authorization: 'Bearer rc_live_client_token' },
  });
  const { items } = await res.json();
  $w('#eventsRepeater').data = items.map((r) => ({ _id: r.id, ...r.fields }));
});
```

## Pipeline config

Import this config into a RealComposer pipeline. `{{secrets.NAME}}` values are read from the encrypted Secret Vault at request time, so the client never sees them.

```json
{
  "name": "Call Airtable API from Wix Velo Securely",
  "method": "GET",
  "cacheTtlSeconds": 300,
  "authMode": "client_token",
  "steps": [
    {
      "stepKey": "airtable",
      "upstreamUrlTemplate": "https://api.airtable.com/v0/{{secrets.AIRTABLE_BASE_ID}}/{{query.table}}?pageSize=50&fields%5B%5D=Title&fields%5B%5D=Date",
      "httpMethod": "GET",
      "headersTemplate": {
        "Authorization": "Bearer {{secrets.AIRTABLE_PAT}}"
      },
      "timeoutMs": 3000
    }
  ],
  "outputMapping": {
    "version": 1,
    "fields": [
      {
        "targetPath": "items",
        "sourceStep": "airtable",
        "sourcePath": "records"
      }
    ]
  }
}
```

## Call the pipeline

```bash
curl -X GET "https://realcomposer.com/v1/workspace/call-airtable-from-wix-velo-securely" \
  -H "Authorization: Bearer rc_live_client_token"
```

## Payload before and after

Raw upstream response:

```json
{
  "records": [
    {
      "id": "recEv1",
      "createdTime": "2026-09-12T08:00:00.000Z",
      "fields": {
        "Title": "Open House",
        "Date": "2026-12-02",
        "Owner": "usr1"
      }
    }
  ],
  "offset": "itrB2/recEv1"
}
```

Slimmed RealComposer response:

```json
{
  "items": [
    {
      "id": "recEv1",
      "createdTime": "2026-09-12T08:00:00.000Z",
      "fields": {
        "Title": "Open House",
        "Date": "2026-12-02"
      }
    }
  ]
}
```

## FAQ

### Why not use the Wix Secrets Manager instead?

The Secrets Manager works from backend web modules. RealComposer adds edge caching, payload slimming, and reuse across other sites and apps with the same pipeline.

### Why does the page code add an _id field?

Wix repeaters require each item to have a unique _id, so the snippet copies the Airtable record id into that key before binding.
