---
title: Quickstart
url: https://docs.schemalabs.ai/quickstart
description: Get an API key, quick-run Schema across sources that share no key, and publish the same configuration as an endpoint, in about five minutes.
---

# Quickstart

> Get an API key, quick-run Schema across sources that share no key, and publish the same configuration as an endpoint, in about five minutes.

## Get an API key

Sign in to the [platform](https://platform.schemalabs.ai) and open **API** in the left nav (available from Plus; see [Plans](https://docs.schemalabs.ai/billing#plans)). Create a key with the `read`, `run`, `manage`, and `serve` scopes: one key covers this walkthrough. Production systems call a live endpoint with a separate key scoped to `serve` only (see [Authentication](https://docs.schemalabs.ai/authentication)). The secret (`sk_live_...`) is shown once; store it in your secrets manager. Export it for the shell you will use:

```bash
export SCHEMA_API_KEY="sk_live_..."
```

## Quick run across sources

Schema takes any number of tables from any number of sources in one call; two are enough to show the cross-table map. Send tables from two systems that describe the same people but share no identifier. Column names in the second table are opaque on purpose: Schema understands the values.

cURL:

```bash
curl -X POST https://api.schemalabs.ai/v2/run \
  -H "Authorization: Bearer $SCHEMA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "tables": [
      { "id": "crm_contacts",
        "columns": ["full_name", "phone", "email", "signup_date"],
        "rows": [
          ["Ana Kaya",    "555-0142", "ana.k@example.com", "2024-11-03"],
          ["Devraj Nair", "555-0199", "d.nair@example.com", "2024-12-01"],
          ["Mira Ostrom", "555-0210", null,                "2025-01-17"]
        ] },
      { "id": "cards_db",
        "columns": ["c0", "c1", "c2", "c3"],
        "rows": [
          ["KAYA, ANA",    "+1 555 0142", "Visa",       12000],
          ["NAIR, DEVRAJ", "+1 555 0199", "Mastercard", 6500],
          ["OSTROM, MIRA", "+1 555 0210", "Amex",       null]
        ] }
    ],
    "target": { "mode": "auto" }
  }'
```
Python:

```python
import os, requests

tables = [
    {"id": "crm_contacts",
     "columns": ["full_name", "phone", "email", "signup_date"],
     "rows": [["Ana Kaya", "555-0142", "ana.k@example.com", "2024-11-03"],
              ["Devraj Nair", "555-0199", "d.nair@example.com", "2024-12-01"],
              ["Mira Ostrom", "555-0210", None, "2025-01-17"]]},
    {"id": "cards_db",
     "columns": ["c0", "c1", "c2", "c3"],
     "rows": [["KAYA, ANA", "+1 555 0142", "Visa", 12000],
              ["NAIR, DEVRAJ", "+1 555 0199", "Mastercard", 6500],
              ["OSTROM, MIRA", "+1 555 0210", "Amex", None]]},
]

r = requests.post(
    "https://api.schemalabs.ai/v2/run",
    headers={"Authorization": f"Bearer {os.environ['SCHEMA_API_KEY']}"},
    json={"tables": tables, "target": {"mode": "auto"}},
)
r.raise_for_status()
bundle = r.json()
```
JavaScript:

```javascript
const tables = [
  { id: 'crm_contacts',
    columns: ['full_name', 'phone', 'email', 'signup_date'],
    rows: [['Ana Kaya', '555-0142', 'ana.k@example.com', '2024-11-03'],
           ['Devraj Nair', '555-0199', 'd.nair@example.com', '2024-12-01'],
           ['Mira Ostrom', '555-0210', null, '2025-01-17']] },
  { id: 'cards_db',
    columns: ['c0', 'c1', 'c2', 'c3'],
    rows: [['KAYA, ANA', '+1 555 0142', 'Visa', 12000],
           ['NAIR, DEVRAJ', '+1 555 0199', 'Mastercard', 6500],
           ['OSTROM, MIRA', '+1 555 0210', 'Amex', null]] },
];

const res = await fetch('https://api.schemalabs.ai/v2/run', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.SCHEMA_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ tables, target: { mode: 'auto' } }),
});
const bundle = await res.json();
```

Headers can be real names or opaque labels; Schema does not need them.

## Read the bundle

The response is the full bundle. The parts worth reading first:

```json
{
  "summary": {
    "tables": 2, "records_linked": 3, "keys_used": "none",
    "prediction": { "target": "cards_db.c2", "task": "classification",
                    "tier": "in_context", "held_out": null }
  },
  "tables": [
    { "id": "crm_contacts",
      "sector": { "top1": { "name": "consumer financial services",
                            "confidence": 0.89 } },
      "column_profile": [ { "name": "phone", "role": "phone",
                            "role_confidence": 0.97, "pii": true }, "..." ] }
  ],
  "cross_table_map": {
    "column_alignment": [
      { "a": "crm_contacts.phone", "b": "cards_db.c1",
        "attribute": "phone", "confidence": 0.96,
        "example": { "a": "555-0142", "b": "+1 555 0142" } },
      { "a": "crm_contacts.full_name", "b": "cards_db.c0",
        "attribute": "name", "confidence": 0.90 }
    ],
    "entity_matches": [ { "a_row": 0, "b_row": 0, "confidence": 0.94,
                          "matched_on": ["phone", "name"] }, "..." ]
  },
  "imputation": {
    "filled": [ { "table": "cards_db", "row": 2, "column": "c3",
                  "value": 8400.0, "method": "schema-2" } ],
    "row_confidence": [ { "table": "cards_db", "row": 2, "confidence": 0.81 } ]
  },
  "target_selection": { "mode": "auto", "column": "cards_db.c2",
    "reason": "most predictable among eligible categorical columns" },
  "prediction": {
    "task_type": "classification", "classes": ["Amex", "Mastercard", "Visa"],
    "results": [ { "row": 0, "label": "Visa", "confidence": 0.94 } ],
    "note": "in-context prediction; create an endpoint for a held-out score."
  }
}
```

`keys_used` is `"none"`: the phone columns were aligned from their values despite different formats, and the names matched despite different order. The missing value in `cards_db.c3` (a credit limit, though the column is unnamed) was filled with a row-level confidence; imputation fills numeric and categorical cells; a missing email stays missing because identifying values (email, phone, name) are never invented. The prediction is in-context and labeled `held_out: null`, which is what the `note` points at: publish the same configuration as an endpoint for a held-out score. Every field is described in [Outputs](https://docs.schemalabs.ai/outputs).

## Publish it as an endpoint

The same configuration, pinned and scored. Pass the same two tables inline (they are ingested as an upload dataset under Data and pinned), or reference registered datasets and connections in `data` instead:

cURL:

```bash
curl -X POST https://api.schemalabs.ai/v2/endpoints \
  -H "Authorization: Bearer $SCHEMA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: <unique key>" \
  -d '{ "name": "card-brand",
        "tables": [
          { "id": "crm_contacts",
            "columns": ["full_name", "phone", "email", "signup_date"],
            "rows": [["Ana Kaya", "555-0142", "ana.k@example.com", "2024-11-03"],
                     ["Devraj Nair", "555-0199", "d.nair@example.com", "2024-12-01"],
                     ["Mira Ostrom", "555-0210", null, "2025-01-17"]] },
          { "id": "cards_db",
            "columns": ["c0", "c1", "c2", "c3"],
            "rows": [["KAYA, ANA", "+1 555 0142", "Visa", 12000],
                     ["NAIR, DEVRAJ", "+1 555 0199", "Mastercard", 6500],
                     ["OSTROM, MIRA", "+1 555 0210", "Amex", null]] }
        ],
        "target": { "mode": "auto" }, "task": { "mode": "auto" } }'
```
Python:

```python
r = requests.post(
    "https://api.schemalabs.ai/v2/endpoints",
    headers={"Authorization": f"Bearer {os.environ['SCHEMA_API_KEY']}",
             "Idempotency-Key": "<unique key>"},
    json={"name": "card-brand", "tables": tables,
          "target": {"mode": "auto"}, "task": {"mode": "auto"}},
)
job = r.json()   # 202: { "endpoint": "card-brand", "id": "...", "status": "creating", "job_id": "job_..." }
```
JavaScript:

```javascript
const res = await fetch('https://api.schemalabs.ai/v2/endpoints', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.SCHEMA_API_KEY}`,
    'Content-Type': 'application/json',
    'Idempotency-Key': '<unique key>',
  },
  body: JSON.stringify({ name: 'card-brand', tables,
    target: { mode: 'auto' }, task: { mode: 'auto' } }),
});
const job = await res.json(); // 202
```

The response is `202` with the endpoint id and a `job_id`. `base` was omitted, so the endpoint is created on the latest Schema model (name one to fix the base). Creation pins the data, evaluates held-out, and exposes the URL, typically in minutes. Poll `GET /v2/jobs/{job_id}` until `status` is `done`, or watch the endpoint flip from `creating` to `live` on the platform.

## Serve it

Once live, POST new rows to the endpoint URL (the key needs the `serve` scope) and get the bundle back, scored by the endpoint over its pinned data. The held-out report lives on the endpoint object and at `/v2/reports/{id}`:

```bash
curl -X POST https://api.schemalabs.ai/v2/serve/{endpoint_id} \
  -H "Authorization: Bearer $SCHEMA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "tables": [ { "id": "crm_contacts",
                      "columns": ["full_name","phone","email","signup_date"],
                      "rows": [["Yuki Sato","555-0301",
                                "y.sato@example.com","2025-08-01"]] } ] }'
```

## Next

- [Endpoints](https://docs.schemalabs.ai/endpoints): State, refresh, upgrade, and what the URL guarantees.
- [Held-out reports](https://docs.schemalabs.ai/held-out): How the score is computed and what the report contains.
- [Usage and billing](https://docs.schemalabs.ai/billing): Exactly what each of these calls drew.
