> ## Documentation Index
> Fetch the complete documentation index at: https://docs.gavai.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Your first integration

> Authenticate, read a page, patch a block, publish — end to end from your own code.

export const ErrorTable = ({errors}) => <div className="gv-error-table" role="table" aria-label="Errors and fixes">
    <div className="gv-error-table__head" role="row">
      <span className="gv-error-table__col-head" role="columnheader">Error</span>
      <span className="gv-error-table__col-head" role="columnheader">Cause</span>
      <span className="gv-error-table__col-head" role="columnheader">Fix</span>
    </div>
    {errors.map((err, i) => <div className="gv-error-table__row" role="row" key={i}>
        <code className="gv-error-table__code" role="cell">{err.code}</code>
        <div className="gv-error-table__cause" role="cell">{err.cause}</div>
        <div className="gv-error-table__fix" role="cell">{err.fix}</div>
      </div>)}
  </div>;

export const ExpectedOutput = ({caption = "Expected output", children}) => <div className="gv-output">
    <div className="gv-output__label">
      <svg className="gv-output__icon" viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
        <polyline points="15 10 20 15 15 20" />
        <path d="M4 4v7a4 4 0 0 0 4 4h12" />
      </svg>
      <span>{caption}</span>
    </div>
    <div className="gv-output__body">{children}</div>
  </div>;

export const PageMeta = ({audience, readingTime, prereqs, level}) => {
  const items = [audience && ({
    key: "audience",
    label: "AUDIENCE",
    value: audience
  }), readingTime && ({
    key: "time",
    label: "READ",
    value: readingTime
  }), level && ({
    key: "level",
    label: "LEVEL",
    value: level
  }), prereqs && prereqs.length > 0 && ({
    key: "prereqs",
    label: "PREREQS",
    value: prereqs.join(" · ")
  })].filter(Boolean);
  if (items.length === 0) return null;
  return <div className="gv-page-meta" role="group" aria-label="Page metadata">
      {items.map(item => <div className="gv-page-meta__item" key={item.key}>
          <span className="gv-page-meta__label">{item.label}</span>
          <span className="gv-page-meta__value">{item.value}</span>
        </div>)}
    </div>;
};

export const BrandCard = ({eyebrow, title, href, icon, badge, cta, featured = false, horizontal = false, children}) => {
  const classes = ["gv-card", featured && "gv-card--featured", horizontal && "gv-card--horizontal"].filter(Boolean).join(" ");
  const inner = <>
      {icon && <span className="gv-card__icon" aria-hidden="true">
          <Icon icon={icon} />
        </span>}
      <div className="gv-card__main">
        {(eyebrow || badge) && <div className="gv-card__meta">
            {eyebrow && <Eyebrow label={eyebrow} tone="muted" />}
            {badge && <span className="gv-card__badge">{badge}</span>}
          </div>}
        <h3 className="gv-card__title">{title}</h3>
        {children && <div className="gv-card__body">{children}</div>}
        {cta && <span className="gv-card__cta">
            {cta}
            <svg className="gv-card__cta-arrow" viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
              <line x1="7" y1="17" x2="17" y2="7" />
              <polyline points="7 7 17 7 17 17" />
            </svg>
          </span>}
      </div>
    </>;
  return href ? <a className={classes} href={href}>
      {inner}
    </a> : <div className={classes}>{inner}</div>;
};

export const NumberedStep = ({n, title, children}) => <li className="gv-step">
    <span className="gv-step__index">{n}</span>
    <div className="gv-step__body">
      <h3 className="gv-step__title">{title}</h3>
      <div className="gv-step__content">{children}</div>
    </div>
  </li>;

export const NumberedSteps = ({children}) => <ol className="gv-steps">{children}</ol>;

export const ArrowItem = ({children}) => <li className="gv-arrow-item">
    <span className="gv-arrow-item__arrow" aria-hidden="true">{"↳︎"}</span>
    <span className="gv-arrow-item__content">{children}</span>
  </li>;

export const ArrowList = ({children}) => <ul className="gv-arrow-list">{children}</ul>;

export const Eyebrow = ({label, tone = "default"}) => <div className={`gv-eyebrow gv-eyebrow--${tone}`}>
    <span className="gv-eyebrow__bar" aria-hidden="true" />
    <span className="gv-eyebrow__label">{label}</span>
  </div>;

<Eyebrow label="GUIDE" />

<PageMeta audience="Developers" readingTime="10 min" level="Beginner" prereqs={["gavAI workspace", "Test API key", "Node 18+ or Bun"]} />

By the end of this guide you will have authenticated against the API, fetched a live page document, patched one of its blocks, and published the change — all from your own code. Plan on about 10 minutes if you already have a workspace and a test API key.

## Prerequisites

<ArrowList>
  <ArrowItem>A gavAI workspace. The examples use the slug `acme` — substitute your own.</ArrowItem>
  <ArrowItem>A test API key starting with `gak_test_`. Find it under **Workspace → Settings → API keys** in the Builder.</ArrowItem>
  <ArrowItem>Node.js 18+ or Bun installed locally.</ArrowItem>
</ArrowList>

## Steps

<NumberedSteps>
  <NumberedStep title="Install the SDK">
    Install the high-level client. The package is `@gavai/sdk` — not `@gavai/sdk-typescript`, which is the low-level escape hatch and is not covered here.

    <CodeGroup>
      ```bash bun theme={null}
      bun add @gavai/sdk
      ```

      ```bash npm theme={null}
      npm install @gavai/sdk
      ```
    </CodeGroup>
  </NumberedStep>

  <NumberedStep title="Authenticate">
    Construct a client with your API key. Read the key from the environment — never hard-code it.

    ```ts theme={null}
    import { Gavai } from "@gavai/sdk";

    const gv = new Gavai({
      apiKey: process.env.GAVAI_API_KEY, // gak_test_* for test, gak_live_* for production
    });
    ```

    The constructor also accepts a `sessionToken` (`gst_*`) for OAuth flows. API keys are the right choice for server-side scripts.
  </NumberedStep>

  <NumberedStep title="Confirm the connection">
    Fetch your profile and list your workspaces. This is the cheapest call that proves auth is wired correctly.

    ```ts theme={null}
    const me = await gv.me.get();
    console.log("Authenticated as:", me.email);

    const { workspaces } = await gv.workspaces.list();
    console.log("Workspaces:", workspaces.map((w) => w.slug));
    ```

    <ExpectedOutput caption="Response from me.get()">
      ```json theme={null}
      {
        "email": "ada@example.com",
        "user_id": "usr_01H8X3K2YQZ4V7N9P5R6S7T8U9"
      }
      ```
    </ExpectedOutput>

    If you see `token_expired` or `insufficient_scope`, regenerate the key in the Builder and try again.
  </NumberedStep>

  <NumberedStep title="Fetch a page">
    Read a specific page document by workspace slug and page id. Find the id under **Page → Settings** in the Builder, or call `gv.pages.list({ workspace: "acme" })` and read the `id` from any element.

    ```ts theme={null}
    const page = await gv.pages.get({
      workspace: "acme",
      id: "page_01H8X4N5P6Q7R8S9T0U1V2W3X4",
    });

    console.log("Page slug:", page.metadata.slug);
    console.log("Root block:", page.tree.block);
    ```

    The returned document follows the page document shape — a `version`, `metadata`, `schema`, and a `tree` of blocks. See [page document](/pageblocks/page-document) for the full schema.
  </NumberedStep>

  <NumberedStep title="Patch a block">
    Pages are edited with [RFC 6902 JSON Patch](https://datatracker.ietf.org/doc/html/rfc6902) operations. Paths follow the block tree: `/tree/children/0/props/headline` targets the `headline` prop of the first child block.

    ```ts theme={null}
    await gv.pages.patch({
      workspace: "acme",
      id: page.id,
      operations: [
        {
          op: "replace",
          path: "/tree/children/0/props/headline",
          value: "Welcome to Acme",
        },
      ],
    });
    ```

    If you are not sure of the exact path, fetch the page and log the tree, then count children. The Builder MCP also exposes `get_block_schema` for introspecting any block's prop shape. See [JSON Patch reference](/pageblocks/json-patch) for the full operation set.
  </NumberedStep>

  <NumberedStep title="Publish">
    Patching saves a draft. Publishing writes every pending draft for the workspace to the live version — what end-users see.

    ```ts theme={null}
    await gv.pages.publish({ workspace: "acme" });
    console.log("Published.");
    ```

    <Warning>
      `publish` writes every pending draft in the workspace at once. If you have multiple pages in draft, they all go live together. Scope your changes carefully in production.
    </Warning>
  </NumberedStep>
</NumberedSteps>

## Troubleshooting

<ErrorTable
  errors={[
{
  code: "token_expired / insufficient_scope",
  cause: "The key is no longer valid or lacks the scopes for the call.",
  fix: "Regenerate the key in the Builder; verify it starts with gak_test_ for test or gak_live_ for production.",
},
{
  code: "not_found on pages.get",
  cause: "The page id or workspace slug is wrong.",
  fix: "Confirm both with gv.pages.list({ workspace: 'acme' }).",
},
{
  code: "JSON Patch path error",
  cause: "The path is targeting a node that does not exist in the tree.",
  fix: "Log the tree first and count children from zero before retrying.",
},
]}
/>

## What you built

You installed the SDK, authenticated with an API key, read your profile, fetched a page document, patched a block prop with a JSON Patch operation, and published the change. Every step above is also available through the Builder UI and the Builder MCP — the underlying model is the same JSON document either way.

## Next steps

<Columns cols={2}>
  <BrandCard title="Accept payments" href="/guides/accept-payments" cta="Read guide">
    Wire a Cta button to Stripe Checkout via the collectPayment capability.
  </BrandCard>

  <BrandCard title="Send transactional email" href="/guides/send-email" cta="Read guide">
    Trigger a templated email on every form submission via sendEmail.
  </BrandCard>

  <BrandCard title="API reference" href="/api-reference/introduction" cta="Browse reference">
    Full reference for every resource and endpoint in the gavAI public API.
  </BrandCard>
</Columns>
