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

# Bibliotecas e Clientes HTTP

> Snippets prontos para integrar com a Vendaze API em Node.js, Python e Go.

Não existem SDKs oficiais da Vendaze. A API segue padrões REST e HTTP convencionais e funciona com qualquer cliente HTTP. Os snippets abaixo cobrem as operações mais comuns em três linguagens. Copie, adapte e use.

<Note>
  Todos os exemplos assumem que você já completou o fluxo OAuth e tem um `access_token` e
  `refresh_token` válidos. Veja [Autenticação](/pt/guides/authentication) se ainda não fez isso.
</Note>

## Node.js

Usa a API `fetch` nativa (Node.js 18+). Sem dependências externas.

<Tabs>
  <Tab title="Troca de token OAuth">
    ```javascript theme={null}
    // Trocar um authorization code por tokens
    async function exchangeCode({ code, clientId, clientSecret }) {
      const res = await fetch('https://api.vendaze.com/oauth/token', {
        method: 'POST',
        headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
        body: new URLSearchParams({
          grant_type: 'authorization_code',
          code,
          client_id: clientId,
          client_secret: clientSecret,
        }),
      });

      if (!res.ok) {
        const { error } = await res.json();
        throw new Error(`${error.code}: ${error.message}`);
      }

      return res.json();
      // { access_token, refresh_token, token_type, expires_in, workspace_slug }
    }
    ```
  </Tab>

  <Tab title="Renovar access token">
    ```javascript theme={null}
    // Renovar um access token expirado
    async function refreshToken({ refreshToken, clientId, clientSecret }) {
      const res = await fetch('https://api.vendaze.com/oauth/token', {
        method: 'POST',
        headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
        body: new URLSearchParams({
          grant_type: 'refresh_token',
          refresh_token: refreshToken,
          client_id: clientId,
          client_secret: clientSecret,
        }),
      });

      if (!res.ok) {
        const { error } = await res.json();
        throw new Error(`${error.code}: ${error.message}`);
      }

      return res.json();
    }
    ```
  </Tab>

  <Tab title="Buscar pessoa por ID">
    ```javascript theme={null}
    // Buscar uma pessoa por ID
    async function getPerson(accessToken, personId) {
      const res = await fetch(
        `https://api.vendaze.com/v1/people/${personId}`,
        { headers: { Authorization: `Bearer ${accessToken}` } }
      );

      if (!res.ok) {
        const { error } = await res.json();
        throw new Error(`${error.code}: ${error.message}`);
      }

      const { data } = await res.json();
      return data;
    }
    ```
  </Tab>

  <Tab title="Criar uma pessoa">
    ```javascript theme={null}
    // Criar um novo contato no workspace
    async function createPerson(accessToken, payload) {
      const res = await fetch('https://api.vendaze.com/v1/people', {
        method: 'POST',
        headers: {
          Authorization: `Bearer ${accessToken}`,
          'Content-Type': 'application/json',
          'Idempotency-Key': crypto.randomUUID(),
        },
        body: JSON.stringify(payload),
      });

      if (!res.ok) {
        const { error } = await res.json();
        if (error.code === 'validation_error') {
          console.error('Validação falhou:', error.fields);
        }
        throw new Error(`${error.code}: ${error.message}`);
      }

      const { data } = await res.json();
      return data;
    }

    // Uso
    const person = await createPerson(accessToken, {
      full_name: 'Ana Costa',
      emails: [{ email: 'ana@empresa.com', type: 'work' }],
      phones: [{ phone: '+5511999990001', type: 'mobile' }],
      position: 'Head of Sales',
    });
    ```
  </Tab>

  <Tab title="Cliente com auto-refresh">
    ```javascript theme={null}
    // Cliente reutilizável com renovação automática de token
    class VendazeClient {
      constructor({ accessToken, refreshToken, clientId, clientSecret, onTokenRefresh }) {
        this.accessToken = accessToken;
        this.refreshToken = refreshToken;
        this.clientId = clientId;
        this.clientSecret = clientSecret;
        this.onTokenRefresh = onTokenRefresh; // callback para persistir novos tokens
      }

      async request(path, options = {}) {
        const res = await fetch(`https://api.vendaze.com${path}`, {
          ...options,
          headers: {
            Authorization: `Bearer ${this.accessToken}`,
            'Content-Type': 'application/json',
            ...options.headers,
          },
        });

        if (res.status === 401) {
          const { error } = await res.json();
          if (error.code === 'token_expired') {
            await this._refresh();
            return this.request(path, options); // retentar uma vez
          }
          throw new Error(`${error.code}: ${error.message}`);
        }

        if (!res.ok) {
          const { error } = await res.json();
          throw new Error(`${error.code}: ${error.message}`);
        }

        if (res.status === 204) return null;
        return res.json();
      }

      async _refresh() {
        const res = await fetch('https://api.vendaze.com/oauth/token', {
          method: 'POST',
          headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
          body: new URLSearchParams({
            grant_type: 'refresh_token',
            refresh_token: this.refreshToken,
            client_id: this.clientId,
            client_secret: this.clientSecret,
          }),
        });

        if (!res.ok) throw new Error('Refresh token expirado. Usuário precisa re-autorizar.');

        const tokens = await res.json();
        this.accessToken = tokens.access_token;
        this.refreshToken = tokens.refresh_token;

        if (this.onTokenRefresh) {
          await this.onTokenRefresh(tokens);
        }
      }
    }

    // Uso
    const client = new VendazeClient({
      accessToken: storedAccessToken,
      refreshToken: storedRefreshToken,
      clientId: process.env.VENDAZE_CLIENT_ID,
      clientSecret: process.env.VENDAZE_CLIENT_SECRET,
      onTokenRefresh: async (tokens) => {
        await db.updateTokens(workspaceSlug, tokens);
      },
    });

    const { data: people } = await client.request('/v1/people/PERSON_UUID');
    ```
  </Tab>
</Tabs>

***

## Python

Usa a biblioteca `httpx` (async). Instale com `pip install httpx`.

<Tabs>
  <Tab title="Troca de token OAuth">
    ```python theme={null}
    import httpx

    async def exchange_code(code: str, client_id: str, client_secret: str) -> dict:
        async with httpx.AsyncClient() as client:
            res = await client.post(
                "https://api.vendaze.com/oauth/token",
                data={
                    "grant_type": "authorization_code",
                    "code": code,
                    "client_id": client_id,
                    "client_secret": client_secret,
                },
            )

        res.raise_for_status()
        return res.json()
        # { "access_token": ..., "refresh_token": ..., "workspace_slug": ... }
    ```
  </Tab>

  <Tab title="Buscar pessoa por ID">
    ```python theme={null}
    import httpx

    async def get_person(access_token: str, person_id: str) -> dict:
        headers = {"Authorization": f"Bearer {access_token}"}

        async with httpx.AsyncClient() as client:
            res = await client.get(
                f"https://api.vendaze.com/v1/people/{person_id}",
                headers=headers,
            )
            res.raise_for_status()
            return res.json()["data"]
    ```
  </Tab>

  <Tab title="Criar uma pessoa">
    ```python theme={null}
    import httpx
    import uuid

    async def create_person(access_token: str, payload: dict) -> dict:
        async with httpx.AsyncClient() as client:
            res = await client.post(
                "https://api.vendaze.com/v1/people",
                json=payload,
                headers={
                    "Authorization": f"Bearer {access_token}",
                    "Idempotency-Key": str(uuid.uuid4()),
                },
            )

        if res.status_code == 422:
            error = res.json()["error"]
            raise ValueError(f"Validação falhou: {error['fields']}")

        res.raise_for_status()
        return res.json()["data"]

    # Uso
    person = await create_person(access_token, {
        "full_name": "Ana Costa",
        "emails": [{"email": "ana@empresa.com", "type": "work"}],
        "phones": [{"phone": "+5511999990001", "type": "mobile"}],
        "position": "Head of Sales",
    })
    ```
  </Tab>

  <Tab title="Cliente com auto-refresh">
    ```python theme={null}
    import httpx
    import uuid
    from typing import Callable, Awaitable

    class VendazeClient:
        def __init__(
            self,
            access_token: str,
            refresh_token: str,
            client_id: str,
            client_secret: str,
            on_token_refresh: Callable[[dict], Awaitable[None]] | None = None,
        ):
            self.access_token = access_token
            self.refresh_token = refresh_token
            self.client_id = client_id
            self.client_secret = client_secret
            self.on_token_refresh = on_token_refresh

        async def request(self, method: str, path: str, **kwargs) -> dict | None:
            async with httpx.AsyncClient() as client:
                res = await client.request(
                    method,
                    f"https://api.vendaze.com{path}",
                    headers={"Authorization": f"Bearer {self.access_token}"},
                    **kwargs,
                )

            if res.status_code == 401:
                error = res.json()["error"]
                if error["code"] == "token_expired":
                    await self._refresh()
                    return await self.request(method, path, **kwargs)
                raise PermissionError(f"{error['code']}: {error['message']}")

            if res.status_code == 204:
                return None

            res.raise_for_status()
            return res.json()

        async def _refresh(self):
            async with httpx.AsyncClient() as client:
                res = await client.post(
                    "https://api.vendaze.com/oauth/token",
                    data={
                        "grant_type": "refresh_token",
                        "refresh_token": self.refresh_token,
                        "client_id": self.client_id,
                        "client_secret": self.client_secret,
                    },
                )

            if not res.is_success:
                raise PermissionError("Refresh token expirado. Usuário precisa re-autorizar.")

            tokens = res.json()
            self.access_token = tokens["access_token"]
            self.refresh_token = tokens["refresh_token"]

            if self.on_token_refresh:
                await self.on_token_refresh(tokens)
    ```
  </Tab>
</Tabs>

***

## Go

Usa apenas a biblioteca padrão. Sem dependências externas.

<Tabs>
  <Tab title="Troca de token OAuth">
    ```go theme={null}
    package vendaze

    import (
        "encoding/json"
        "fmt"
        "net/http"
        "net/url"
        "strings"
    )

    type TokenResponse struct {
        AccessToken   string `json:"access_token"`
        RefreshToken  string `json:"refresh_token"`
        TokenType     string `json:"token_type"`
        ExpiresIn     int    `json:"expires_in"`
        WorkspaceSlug string `json:"workspace_slug"`
    }

    func ExchangeCode(code, clientID, clientSecret string) (*TokenResponse, error) {
        form := url.Values{
            "grant_type":    {"authorization_code"},
            "code":          {code},
            "client_id":     {clientID},
            "client_secret": {clientSecret},
        }

        res, err := http.Post(
            "https://api.vendaze.com/oauth/token",
            "application/x-www-form-urlencoded",
            strings.NewReader(form.Encode()),
        )
        if err != nil {
            return nil, err
        }
        defer res.Body.Close()

        if res.StatusCode != http.StatusOK {
            var errBody struct {
                Error struct {
                    Code    string `json:"code"`
                    Message string `json:"message"`
                } `json:"error"`
            }
            json.NewDecoder(res.Body).Decode(&errBody)
            return nil, fmt.Errorf("%s: %s", errBody.Error.Code, errBody.Error.Message)
        }

        var tokens TokenResponse
        if err := json.NewDecoder(res.Body).Decode(&tokens); err != nil {
            return nil, err
        }
        return &tokens, nil
    }
    ```
  </Tab>

  <Tab title="Buscar pessoa por ID">
    ```go theme={null}
    package vendaze

    import (
        "encoding/json"
        "fmt"
        "net/http"
    )

    type Person struct {
        ID        string `json:"id"`
        FullName  string `json:"full_name"`
        CreatedAt string `json:"created_at"`
    }

    func GetPerson(accessToken string, personID string) (*Person, error) {
        client := &http.Client{}

        req, _ := http.NewRequest("GET",
            fmt.Sprintf("https://api.vendaze.com/v1/people/%s", personID),
            nil,
        )
        req.Header.Set("Authorization", "Bearer "+accessToken)

        res, err := client.Do(req)
        if err != nil {
            return nil, err
        }
        defer res.Body.Close()

        if res.StatusCode != http.StatusOK {
            return nil, fmt.Errorf("status inesperado: %d", res.StatusCode)
        }

        var body struct {
            Data Person `json:"data"`
        }
        if err := json.NewDecoder(res.Body).Decode(&body); err != nil {
            return nil, err
        }

        return &body.Data, nil
    }
    ```
  </Tab>

  <Tab title="Criar uma pessoa">
    ```go theme={null}
    package vendaze

    import (
        "bytes"
        "encoding/json"
        "fmt"
        "net/http"

        "github.com/google/uuid"
    )

    type CreatePersonRequest struct {
        FullName string         `json:"full_name"`
        Emails   []ContactEmail `json:"emails,omitempty"`
        Phones   []ContactPhone `json:"phones,omitempty"`
        Position string         `json:"position,omitempty"`
    }

    type ContactEmail struct {
        Email string `json:"email"`
        Type  string `json:"type,omitempty"`
    }

    type ContactPhone struct {
        Phone string `json:"phone"`
        Type  string `json:"type,omitempty"`
    }

    func CreatePerson(accessToken string, payload CreatePersonRequest) (*Person, error) {
        body, _ := json.Marshal(payload)

        req, _ := http.NewRequest("POST", "https://api.vendaze.com/v1/people", bytes.NewReader(body))
        req.Header.Set("Authorization", "Bearer "+accessToken)
        req.Header.Set("Content-Type", "application/json")
        req.Header.Set("Idempotency-Key", uuid.New().String())

        res, err := (&http.Client{}).Do(req)
        if err != nil {
            return nil, err
        }
        defer res.Body.Close()

        if res.StatusCode != http.StatusCreated {
            var errBody struct {
                Error struct {
                    Code    string            `json:"code"`
                    Message string            `json:"message"`
                    Fields  map[string]string `json:"fields,omitempty"`
                } `json:"error"`
            }
            json.NewDecoder(res.Body).Decode(&errBody)
            return nil, fmt.Errorf("%s: %s", errBody.Error.Code, errBody.Error.Message)
        }

        var result struct {
            Data Person `json:"data"`
        }
        if err := json.NewDecoder(res.Body).Decode(&result); err != nil {
            return nil, err
        }
        return &result.Data, nil
    }
    ```
  </Tab>
</Tabs>

***

## Recursos da comunidade

Nenhum SDK oficial existe ainda. Se você construir uma biblioteca cliente para a Vendaze, abra um pull request no repositório [vendaze/vendaze-api](https://github.com/vendaze) para ser listado aqui.
