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

# Building projects with the API

> Complete guide to building scripts, backends, automations, and assistants on top of Symphony, with ready-made recipes.

## What it is

This guide is for anyone who's going to **build something** on top of Symphony: a script, a backend, an automation, an internal assistant, an agent that runs on its own. It assumes you know how to code but **don't have access to Symphony's code**. Everything here can be done with just your API key and an HTTP client.

If you only want the quick steps to generate the key and make the first call, start with [Connect via API](/en/desenvolvedores/api/conectar-api) and come back here when you're building the real project.

<Info>
  **The core idea:** Symphony's chat API **speaks the same protocol as OpenAI**. If your project already uses the `openai` library (Python, Node, LangChain, LlamaIndex, Vercel AI SDK…), you change the URL and the key and the rest of the code stays the same. The difference is that you now use your company's models, agents, and knowledge bases, with usage controlled by your organization.
</Info>

## Prerequisites

* An active **AI Symphony** account.
* An **API key** (starts with `sk-`).
* Your **company ID**, for the `X-Enterprise-Id` header.
* Any HTTP client (cURL, `requests`, `fetch`, Postman) or an OpenAI-compatible library.

## 1. Setting up access

### 1.1 Generate your API key

<Steps>
  <Step title="Click your username">
    Open **Symphony** and click your name in the bottom-left corner.
  </Step>

  <Step title="Open API keys">
    Go to **Settings** → **Account** tab (or **Connections**, depending on the version) → **API keys**.
  </Step>

  <Step title="Generate and copy the key">
    Click to generate the key and **copy the full value**. It starts with `sk-`.
  </Step>
</Steps>

<Warning>
  **You only have one key.** Generating a new one **replaces** the previous one: every integration that used the old key stops working right away. Before you generate again, make sure no project in production depends on the current key.
</Warning>

### 1.2 Find your company ID

Symphony serves several companies, and **each has its own catalog of models, agents, and knowledge bases**. Since the API key doesn't carry this information, you need to say which company you're working in, with the header:

```
X-Enterprise-Id: fcamara-0000-0000-0000-000000000001
```

Ask **your company's Symphony administrator** for this ID. They can find it in the admin panel.

<Warning>
  This is the number 1 mistake for newcomers: **without this header, you get "model not found" for a model you can see working in the interface.** The system doesn't know which company you mean and falls back to the environment's default catalog, which is probably not yours.
</Warning>

### 1.3 The three headers you'll always use

| Header | Value | When |
| - | - | - |
| `Authorization` | `Bearer sk-...` | Always |
| `X-Enterprise-Id` | Your company ID | Always |
| `Content-Type` | `application/json` | On requests with a JSON body |

That's it. **You don't need cookies or a CSRF token.** Those steps appear in documents describing how the *browser* talks to Symphony. Integrations that use an API key skip them.

**Base URL:** `https://symphony.fcamara.com`

## 2. Your first call

```bash theme={null}
curl -X POST https://symphony.fcamara.com/api/chat/completions \
  -H "Authorization: Bearer sk-..." \
  -H "X-Enterprise-Id: YOUR_COMPANY_ID" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "azure.gpt-5-chat",
    "messages": [
      { "role": "user", "content": "Explain what an API is in two sentences." }
    ]
  }'
```

The response comes in the classic OpenAI format:

```json theme={null}
{
  "id": "chatcmpl-DXttDwwlWkL6YkzARekAPP2cuFUX2",
  "object": "chat.completion",
  "model": "azure.gpt-5-chat",
  "choices": [
    {
      "index": 0,
      "finish_reason": "stop",
      "message": { "role": "assistant", "content": "An API is..." }
    }
  ],
  "usage": { "prompt_tokens": 24, "completion_tokens": 61, "total_tokens": 85 }
}
```

<Tip>
  **Only `model` and `messages` are required.** If you saw somewhere that you need to send `id`, `chat_id`, and `session_id`, that describes what the Symphony interface sends, not an API requirement. You only need those fields if you want the conversation to show up in the platform's history (see section **8. Keeping conversation history**).
</Tip>

### What else you can send

| Field | What for |
| - | - |
| `stream` | `true` returns the response in real time, token by token |
| `temperature` | How creative the response is |
| `max_tokens` | Response length limit |
| `top_p` | Alternative randomness control |
| `stop` | Sequences that stop generation |
| `stream_options` | `{"include_usage": true}` to get the token count when streaming |
| `response_format` | Forces the response to follow a JSON format (see recipe **5.3**) |
| `tools` / `tool_choice` | Function calling: the model asks, your code executes |
| `files` | Attach documents or knowledge bases (see section **7**) |
| `features` | Turn on web search, image generation, or code interpreter, when your account has permission |
| `chat_id` / `id` | Save the conversation in Symphony's history |

<Note>
  Not every model accepts every parameter. Some reject `stop`, others only accept the default temperature. When that happens, Symphony returns a message explaining exactly which parameter was the problem.
</Note>

## 3. Finding the models and agents you can use

**Never guess the `model` value.** Identifiers are defined by your company and change from environment to environment.

```bash theme={null}
curl https://symphony.fcamara.com/api/models \
  -H "Authorization: Bearer sk-..." \
  -H "X-Enterprise-Id: YOUR_COMPANY_ID"
```

```json theme={null}
{
  "data": [
    { "id": "azure.gpt-5-chat", "name": "Azure gpt-5-chat", "owned_by": "openai" },
    { "id": "fc-code-assistant", "name": "Code Vader", "info": { "base_model_id": "azure.gpt-5-chat" } }
  ]
}
```

Three important things about this list:

* **It's already filtered to what your account can use.** Disabled models, agents nobody shared with you, and models outside the company catalog simply don't appear. If something is in your interface but not here, the problem is almost always `X-Enterprise-Id`.
* **Agents appear alongside models.** Every item with `info.base_model_id` is an agent created on the platform. Using an agent is the API's most powerful shortcut: the system prompt, knowledge bases, and tools configured in it are applied automatically, without you sending anything beyond `model`.
* **Copy the `id` exactly as returned.** Using the model name from the original provider (`gemini-2.5-pro`) instead of the Symphony identifier (`FC.gemini-2.5-pro`) is the most common cause of 404 errors.

## 4. Connecting your project with a ready-made library

Since the API is OpenAI-compatible, you don't need to write an HTTP client by hand. Point `base_url` to `https://symphony.fcamara.com/api`: the library appends `/chat/completions` on its own.

<CodeGroup>
  ```python Python theme={null}
  from openai import OpenAI

  client = OpenAI(
      api_key="sk-...",
      base_url="https://symphony.fcamara.com/api",
      default_headers={"X-Enterprise-Id": "YOUR_COMPANY_ID"},
  )

  response = client.chat.completions.create(
      model="azure.gpt-5-chat",
      messages=[{"role": "user", "content": "Explain what an API is."}],
  )
  print(response.choices[0].message.content)
  ```

  ```ts Node / TypeScript theme={null}
  import OpenAI from 'openai';

  const client = new OpenAI({
  	apiKey: process.env.SYMPHONY_API_KEY,
  	baseURL: 'https://symphony.fcamara.com/api',
  	defaultHeaders: { 'X-Enterprise-Id': process.env.SYMPHONY_ENTERPRISE_ID! }
  });

  const response = await client.chat.completions.create({
  	model: 'azure.gpt-5-chat',
  	messages: [{ role: 'user', content: 'Explain what an API is.' }]
  });
  console.log(response.choices[0].message.content);
  ```

  ```python LangChain theme={null}
  from langchain_openai import ChatOpenAI

  llm = ChatOpenAI(
      model="azure.gpt-5-chat",
      api_key="sk-...",
      base_url="https://symphony.fcamara.com/api",
      default_headers={"X-Enterprise-Id": "YOUR_COMPANY_ID"},
  )
  ```

  ```python Python (requests) theme={null}
  import os
  import requests

  BASE_URL = "https://symphony.fcamara.com"
  HEADERS = {
      "Authorization": f"Bearer {os.environ['SYMPHONY_API_KEY']}",
      "X-Enterprise-Id": os.environ["SYMPHONY_ENTERPRISE_ID"],
      "Content-Type": "application/json",
  }

  response = requests.post(
      f"{BASE_URL}/api/chat/completions",
      headers=HEADERS,
      json={
          "model": "azure.gpt-5-chat",
          "messages": [{"role": "user", "content": "Explain what an API is."}],
      },
      timeout=(10, 300),  # 10s to connect, 300s for the response to finish
  )
  response.raise_for_status()
  print(response.json()["choices"][0]["message"]["content"])
  ```
</CodeGroup>

### In VS Code / JetBrains

If your "project" is coding with AI assistance in the editor, you don't need code: see [IDE Plugin](/en/desenvolvedores/plugin-ide).

## 5. Project recipes

### 5.1 Use a company agent as your product's engine

This is the most underrated recipe. Instead of keeping a giant prompt inside your code, create an **agent** in the Symphony interface, with a system prompt, knowledge base, and tools, and call it by `id`:

```json theme={null}
{
  "model": "fc-code-assistant",
  "messages": [{ "role": "user", "content": "Review this function and point out risks." }]
}
```

**Why it's worth it:** the business person or specialist becomes the one who tunes the behavior, directly in the interface, without redeploying your system. Your code is only responsible for the integration.

### 5.2 Assistant with conversation memory

The API is **stateless by default**: each call is independent. To keep context, send the history:

```python theme={null}
history = [{"role": "system", "content": "You are a technical support assistant. Be concise."}]

def ask(question):
    history.append({"role": "user", "content": question})
    r = client.chat.completions.create(model="azure.gpt-5-chat", messages=history)
    answer = r.choices[0].message.content
    history.append({"role": "assistant", "content": answer})
    return answer
```

<Warning>
  **History grows and cost grows with it**: you pay tokens for everything you resend. In long conversations, trim the oldest messages, or ask the model for a periodic summary of what was discussed and replace the old part with that summary. If you exceed the model's window, you get a context limit error.

  There's an alternative where Symphony keeps the history for you. See section **8. Keeping conversation history**.
</Warning>

### 5.3 Structured data extraction

When you need reliable JSON instead of text, use `response_format`:

```json theme={null}
{
  "model": "azure.gpt-5-chat",
  "messages": [
    { "role": "user", "content": "Extract name and title: 'Ana Souza, product manager'." }
  ],
  "response_format": {
    "type": "json_schema",
    "json_schema": {
      "name": "person",
      "schema": {
        "type": "object",
        "properties": {
          "name": { "type": "string" },
          "title": { "type": "string" }
        },
        "required": ["name", "title"],
        "additionalProperties": false
      }
    }
  }
}
```

Not every model supports this feature. Those that don't return free text. **Always validate the JSON on your side** before trusting it.

### 5.4 Questions and answers about company documents (RAG)

Create the knowledge base in the interface (it's simpler) and then use its `id` in your calls. See section **7. Working with documents and knowledge bases**.

### 5.5 Let the model call your system's functions

The *function calling* pattern works the same as OpenAI's: you describe the functions, the model returns `tool_calls`, **your code executes them** and returns the result as a message with the `tool` role.

```json theme={null}
{
  "model": "azure.gpt-5-chat",
  "messages": [{ "role": "user", "content": "What's the status of order 4821?" }],
  "tools": [
    {
      "type": "function",
      "function": {
        "name": "get_order",
        "description": "Gets the status of an order by its number",
        "parameters": {
          "type": "object",
          "properties": { "number": { "type": "string" } },
          "required": ["number"]
        }
      }
    }
  ],
  "tool_choice": "auto"
}
```

<Note>
  Don't confuse these with the **Tools** registered in Symphony ([Tools](/en/guia/ferramentas)): the platform runs those on its own, and you get them for free when you use an agent that has them configured. The `tools` in this recipe are functions of **your** system, which only your code knows how to run.
</Note>

### 5.6 Batch processing

To classify, summarize, or translate many items:

* **Limit concurrency.** Start with 3 to 5 requests in parallel and increase slowly, watching for `429` errors.
* **Save progress** after each item, so you can resume where you left off.
* **Retry only on the right errors** (see section **9. Errors**).
* **Track usage** at `GET /api/v1/consumption/me`. A large batch can use up your monthly limit before it finishes.

## 6. Real-time responses (streaming)

With `"stream": true`, the response arrives in chunks as it's generated. That's what gives chat interfaces the "typing" feel.

```python theme={null}
for chunk in client.chat.completions.create(
    model="azure.gpt-5-chat",
    messages=[{"role": "user", "content": "Count to five."}],
    stream=True,
):
    piece = chunk.choices[0].delta.content
    if piece:
        print(piece, end="", flush=True)
```

If you read the stream by hand, the format is a sequence of `data: {...}` lines, ending with `data: [DONE]`.

<Warning>
  **Three traps that catch almost everyone:**

  1. **An error can arrive after the `200 OK`.** The status code is sent before the response starts. If the model fails mid-generation, the failure comes as an event inside the stream, not as an HTTP error. Handle it.
  2. **Don't count on one chunk per token.** Events can arrive grouped or split by the network. Buffer by line before parsing.
  3. **`[DONE]` is a signal, not content.** Ignore it and ignore blank lines.
</Warning>

## 7. Working with documents and knowledge bases

### 7.1 Send a document along with the question

<Steps>
  <Step title="Upload the file">
    ```bash theme={null}
    curl -X POST https://symphony.fcamara.com/api/v1/files/ \
      -H "Authorization: Bearer sk-..." \
      -H "X-Enterprise-Id: YOUR_COMPANY_ID" \
      -F "file=@contract.pdf"
    ```

    ```json theme={null}
    { "id": "3f1c...", "filename": "contract.pdf", "data": { "status": "processing" } }
    ```
  </Step>

  <Step title="Wait for processing to finish">
    The upload responds right away, but reading the file (text extraction, OCR) keeps running in the background. Poll until the status leaves `processing`:

    ```bash theme={null}
    curl https://symphony.fcamara.com/api/v1/files/3f1c... \
      -H "Authorization: Bearer sk-..." -H "X-Enterprise-Id: YOUR_COMPANY_ID"
    # data.status: "processing" → "ready" or "error"
    ```
  </Step>

  <Step title="Attach the file to the question">
    ```json theme={null}
    {
      "model": "azure.gpt-5-chat",
      "messages": [{ "role": "user", "content": "Summarize the risks in this contract." }],
      "files": [{ "type": "file", "id": "3f1c..." }]
    }
    ```
  </Step>
</Steps>

<Warning>
  **Large or scanned PDFs can take minutes.** If you attach the file while it's still `processing`, the model responds as if the document were empty. That's a silent bug that's hard to find later.
</Warning>

Documents attached this way go into the context **in full**. That's the right behavior when the file *is* the subject of the conversation.

### 7.2 Use a knowledge base

Knowledge bases are for the opposite case: lots of content, of which only the relevant parts should go into each question.

Create the base in the interface ([Knowledge base](/en/guia/base-conhecimento)), copy its `id`, and use:

```json theme={null}
{
  "model": "azure.gpt-5-chat",
  "messages": [{ "role": "user", "content": "What is the vacation policy?" }],
  "files": [{ "type": "collection", "id": "BASE_ID" }]
}
```

Symphony looks up the relevant parts and injects them into the context automatically. To list the bases you can see, use `GET /api/v1/knowledge/list/compact` (see the [API reference](/en/desenvolvedores/api/referencia-da-api)).

<Tip>
  **A large base gives partial answers to broad questions.** When the base doesn't fit in the model's window, only the most relevant parts go in. Questions like "summarize everything" or "how many documents mention X" tend to come out incomplete. Prefer specific questions, or a model with a larger context window.
</Tip>

If you use an **agent** that already has a base configured, you don't need to send `files`: it brings the base along.

## 8. Keeping conversation history

By default nothing is saved: you send the history on each call and Symphony keeps nothing.

If you want your project's conversations to show up in the platform's history (for auditing, so the user can continue the conversation in the interface, or just so you don't have to manage history), create a chat and reuse the `chat_id`:

```bash theme={null}
curl -X POST https://symphony.fcamara.com/api/v1/chats/new \
  -H "Authorization: Bearer sk-..." \
  -H "X-Enterprise-Id: YOUR_COMPANY_ID" -H "Content-Type: application/json" \
  -d '{"chat": {"title": "My project", "models": ["azure.gpt-5-chat"], "messages": []}}'
# → { "id": "ca49f9ae-...", ... }
```

Then, on the following calls:

```json theme={null}
{
  "model": "azure.gpt-5-chat",
  "chat_id": "ca49f9ae-...",
  "messages": [{ "role": "user", "content": "And what about the deadline?" }]
}
```

**The golden detail:** if you send the `chat_id` and **only the user's new question** (no `assistant` role message in the array), Symphony retrieves the saved history and puts it before your message. In other words: **you can keep a long conversation going by sending only the new question.**

Two conditions for this to work:

* If your array already has an `assistant` message, Symphony assumes you're managing the history and injects nothing.
* In a new conversation, with no saved response yet, there's nothing to inject either.

## 9. Errors: how to interpret them and what to do

Error responses include a stable code in addition to the text. Depending on the endpoint, the code comes in `error.code` or in `detail.code`:

```json theme={null}
{
  "detail": {
    "code": "MODEL_UNAVAILABLE",
    "message": "It may have been removed or may not be available to your account.",
    "action": "Choose another model to continue.",
    "retryable": false,
    "request_id": "abc123..."
  }
}
```

The possible formats and sample code to read both are in [Errors and limits](/en/desenvolvedores/api/erros-e-limites).

<Tip>
  **Code against the `code`, never the message text.** The text changes with language and wording improvements; the code doesn't. And **store the `request_id` in your logs**: that's how the Symphony team can trace what happened when you open a ticket.
</Tip>

### What each status code means here

| Code | What happened | What to do |
| -: | - | - |
| `400` | Invalid body, or a parameter the model doesn't accept | Fix the payload. Retrying won't help |
| `401` | Key invalid, revoked, or replaced | Generate the key again and update **all** integrations |
| `402` | Your **usage limit** was reached, or your access is blocked | Contact the administrator. Retrying never helps |
| `403` | No permission, or API keys disabled in the environment | Check `X-Enterprise-Id` and confirm the policy with the administrator |
| `404` | Model outside the company catalog, or resource doesn't exist | Check `X-Enterprise-Id` and call `GET /api/models` again |
| `409` | The agent exists, but its base model was removed | Fix the agent in the Symphony interface |
| `429` | Too many requests, or company quota used up | Wait and retry with backoff |
| `5xx` | Temporary failure of the service or the AI provider | Retry with exponential backoff |

### Codes you'll see most often

| `code` | Meaning |
| - | - |
| `MODEL_UNAVAILABLE` | The `model` isn't in your company's catalog, or you don't have access to it |
| `AGENT_BASE_MODEL_UNAVAILABLE` | The agent lost its base model and needs to be fixed in the interface |
| `CONTEXT_LIMIT_EXCEEDED` | Your question + history + files exceeded what the model can handle |
| `RATE_LIMITED` | The AI provider temporarily rate-limited you |
| `INVALID_PARAMETER` | A parameter is outside what the model accepts |
| `TIMEOUT`, `NETWORK_ERROR`, `SERVICE_UNAVAILABLE` | Transient failure: worth retrying |
| `STREAM_INTERRUPTED` | The stream ended before the response was complete |
| `CONSUMPTION_LIMIT_REACHED` | Your monthly usage limit ran out |
| `CONSUMPTION_BLOCKED` | Your AI access was blocked by an administrator |
| `FILE_TOO_LARGE`, `FILE_FORMAT_UNSUPPORTED`, `FILE_PROCESSING_FAILED` | Problems with the uploaded file |

### Retry policy

* **Retry** on `429` and `5xx`, with exponential backoff and a bit of randomness, respecting the `Retry-After` header when present.
* **Never retry** `400`, `401`, `402`, `403`, `404`, and `409`. None of them resolves on its own, and insisting only burns your request quota.

## 10. Usage limits

There are **three different limits**, and mixing them up leads to pointless retries:

<AccordionGroup>
  <Accordion title="1. Request limit (429 with Retry-After)" icon="gauge">
    A cap on requests per minute, counted **per IP address**. Responses include `X-RateLimit-Limit`, `X-RateLimit-Remaining`, and `X-RateLimit-Reset` to guide you. Since the limit is per IP, several integrations leaving from the same network share the same quota. Wait and try again.
  </Accordion>

  <Accordion title="2. Company quota (429, with quota_exceeded in the body)" icon="building">
    Token quota contracted by your organization. The body tells you when it renews. Retrying only helps after that.
  </Accordion>

  <Accordion title="3. Your usage limit (402)" icon="wallet">
    Your user's monthly spending cap, or an administrative block. It's checked **before** any call to the model, so nothing is spent. Check it anytime:

    ```bash theme={null}
    curl https://symphony.fcamara.com/api/v1/consumption/me \
      -H "Authorization: Bearer sk-..." -H "X-Enterprise-Id: YOUR_COMPANY_ID"
    ```

    Retrying never resolves a `402`. Only an administrator raising your limit, or the start of the next month.
  </Accordion>
</AccordionGroup>

**Response time:** set generous timeouts. Large models, with lots of context or attached files, can take minutes. A short timeout on your client will cut off legitimate responses.

## 11. Best practices

<Columns cols={2}>
  <Card title="Do" icon="circle-check">
    * **Store the key in an environment variable or secrets vault.**
    * **Always use HTTPS.**
    * **Ask the administrator for a dedicated technical account** for each integration. This prevents regenerating your personal key from breaking the automation, and keeps the project's usage separate from your individual usage in company reports.
    * **Set `X-Enterprise-Id` through configuration**, never inferred at runtime.
    * **Call `GET /api/models` once at startup** and check that the model you expect exists. Failing at startup is much better than failing in the middle of a batch.
    * **Log the `request_id`** of every error response.
    * **Measure usage** before scaling volume.
  </Card>

  <Card title="Don't" icon="circle-xmark">
    * **Don't put the key in code that runs in the browser.** It gives full access to your account, with no origin or scope restriction. If your project has a front end, the key stays in *your* backend, which makes the call.
    * Don't commit the key to a repository, or put it in a URL, log, or query parameter.
    * Don't retry requests that failed with a deterministic `4xx` error.
    * Don't treat the file upload's `200 OK` as "ready to use".
    * Don't assume a model that works today will exist tomorrow: the catalog is managed by your company.
  </Card>
</Columns>

## 12. Frequently asked questions

<AccordionGroup>
  <Accordion title="Do I really need X-Enterprise-Id?">
    Yes, whenever the environment serves more than one company. Without it, Symphony doesn't know which catalog you mean and you get "model not found" for models that exist.
  </Accordion>

  <Accordion title="Do I need a CSRF token?">
    No, when you use an API key without sending cookies. That step applies to anyone reproducing the browser flow with a session token.
  </Accordion>

  <Accordion title="Does my key expire?">
    No. But it's **replaced** if you generate another one, and it can be revoked by the administrator.
  </Accordion>

  <Accordion title="Can I have more than one key, one per project?">
    Not today: it's one key per user. The workaround is to request a **separate technical account** for each integration.
  </Accordion>

  <Accordion title="Does the key have its own permissions?">
    No. It's worth exactly what your account is worth: same models, same agents, same bases, same usage limit. There's no "read-only" key or key restricted to one agent.
  </Accordion>

  <Accordion title="Which model should I use?">
    One of the `id` values returned by `GET /api/models`. If there's a company agent for your use case, prefer the agent: it already comes with prompt, base, and tools set up.
  </Accordion>

  <Accordion title="Can I use the AI libraries and frameworks I already know?">
    Yes, any that are compatible with the OpenAI protocol: the `openai` SDK, LangChain, LlamaIndex, Vercel AI SDK, Continue.dev, and the like.
  </Accordion>

  <Accordion title="How do I know how much I'm spending?">
    `GET /api/v1/consumption/me`. Full reports are available to the company administrator.
  </Accordion>

  <Accordion title="Is there interactive documentation (Swagger)?">
    Not in production. All available endpoints are described in the [API reference](/en/desenvolvedores/api/referencia-da-api).
  </Accordion>

  <Accordion title="I get 401 even with the key I just copied.">
    Check that the header is exactly `Authorization: Bearer sk-...`, with no quotes, no extra space, and the complete key. Also check that nobody generated a new key on the same account.
  </Accordion>

  <Accordion title="The response ignores the file I attached.">
    The file was probably still being processed. Poll `GET /api/v1/files/{id}` until the status is `ready`.
  </Accordion>
</AccordionGroup>

## 13. Known limitations

* **A single key per user**, with no name, no scope of its own, and no rotation without interruption. Generating a new one breaks the previous one immediately.
* **The key equals your entire account.** There's no service credential with reduced permissions.
* **The request limit is per IP, not per key.** Integrations leaving from the same IP share the quota.
* **There's no completion notification (webhook)** for API consumers: integration is synchronous or by streaming.
* **There's no official Symphony library.** Use an OpenAI-compatible one.
* **There's no Swagger in production.**
* **The model catalog is managed by your company** and can change without notice for integrators.
* **Some features depend on permissions** (web search, image generation, code interpreter) and may be turned off for your account.

## 14. Checklist before going to production

* [ ] Key in an environment variable or vault, never in code or the front end
* [ ] Ideally, a dedicated technical account for this integration
* [ ] `X-Enterprise-Id` coming from configuration, present on every call
* [ ] Model validated at startup with `GET /api/models`
* [ ] Error handling by `code` (in `error.code` or `detail.code`), not by text
* [ ] Retry with backoff only on `429` and `5xx`
* [ ] `request_id` recorded in your error logs
* [ ] Client timeout compatible with long responses
* [ ] If streaming: `[DONE]`, split events, and errors after the `200` handled
* [ ] If using files: wait for `ready` status before attaching
* [ ] If the conversation is long: a defined strategy so history doesn't overflow the window
* [ ] Usage monitored at `GET /api/v1/consumption/me`

## Related documents

* [Connect via API](/en/desenvolvedores/api/conectar-api): quickstart
* [API reference](/en/desenvolvedores/api/referencia-da-api): all available endpoints, with parameters and responses
* [Errors and limits](/en/desenvolvedores/api/erros-e-limites): full catalog of error codes and usage limits
* [Environments and support](/en/desenvolvedores/api/ambientes-e-suporte): production, staging, and how to get help
* [IDE Plugin](/en/desenvolvedores/plugin-ide): AI in your editor, without writing an integration
* [Agents](/en/guia/agentes): create the agent your project will call
* [Knowledge base](/en/guia/base-conhecimento): prepare documents for RAG
* [Tools](/en/guia/ferramentas): tools run by the platform
* [Account settings](/en/guia/configuracoes): where your API key lives


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.