# Autenticación

Toda llamada a una API de Inovacc se autentica con un solo encabezado, `Authorization: Bearer <credential>`. La credencial identifica a tu organización y, en el caso de la credencial de una aplicación, también a la aplicación, por lo que nunca las nombras en la solicitud. Las llamadas que cambian algo en la API de IA también llevan un `X-Operation-Id`, que las hace seguras de reintentar. Esta guía explica las credenciales, los encabezados que lee cada API, cómo funciona la idempotencia y cómo mantener las claves seguras durante toda su vida.

## Los encabezados

| Encabezado | Dónde | Valor |
|---|---|---|
| `Authorization` | toda llamada, toda API | `Bearer <credential>` |
| `X-Operation-Id` | API de IA (`ai.inovacc.dev`): todo `POST`, `PUT` y `DELETE`; opcional en `GET` | un id único que eliges: de 8 a 128 letras, dígitos, `_` o `-` |

Datos (`data.inovacc.dev`) y Eventos (`events.inovacc.dev`) no leen ningún encabezado de organización ni de operación: el tenant proviene solo de la credencial, y cualquier encabezado que intente nombrar a otro se ignora. Datos usa versiones HTTP (`If-Match`, `If-None-Match`) y Eventos usa un `idempotency_key` en el cuerpo para reintentos seguros; consulta sus páginas de producto.

Una solicitud sin credencial, con una mal formada o con una que no se reconoce se rechaza antes de que se ejecute nada: `401` con `missing_credentials` o `invalid_credentials`. En todas las API esta respuesta es la misma exista o no la ruta que llamaste, de modo que no se puede aprender nada de la API sin una credencial.

## Obtén una clave

Las **claves secretas** las crea en la consola de Inovacc una persona de tu organización y se muestran **una sola vez**: Inovacc almacena solo un hash, por lo que una clave perdida no se puede mostrar de nuevo, solo reemplazar. Hoy el acceso es por invitación: pide a tu contacto de Inovacc que te invite, y luego crea una clave bajo tu organización.

## Credenciales

| Credencial | Aspecto | Para | Aceptada por |
|---|---|---|---|
| Clave de API secreta | `aik_` seguido de 64 caracteres hexadecimales | tus servidores | IA, Datos, Eventos |
| Token de acceso de cliente OAuth | un token firmado emitido a un cliente de una aplicación | tus servidores | IA, Datos, Eventos |
| Clave publicable | `apb_...` | páginas web, desde los orígenes que su aplicación lista | Datos (registros y archivos, donde las reglas lo permitan), Eventos (solo transmisiones) |
| Token de acceso de usuario final | emitido cuando una persona inicia sesión en tu aplicación | el navegador o la app de esa persona | Datos (registros y archivos, según las reglas de la colección) |

Una clave puede estar **vinculada a una aplicación**. Una clave así alcanza solo lo que esa aplicación ha habilitado: en la API de IA cada ruta necesita una capacidad (`ai.chat`, `ai.embeddings`, `knowledge`, `decision`), y de lo contrario se rechaza con `403 capability_not_enabled`; en Datos ve solo las bases de datos que creó su aplicación y la carpeta de archivos de su aplicación; en Eventos puede nombrar solo su propio espacio de trabajo.

**Las claves publicables no son secretos.** Están pensadas para incluirse dentro de una página web. Lo que protege tus datos es la lista de orígenes permitidos y, sobre todo, las reglas de cada colección: una clave publicable no puede hacer nada que una regla no admita.

## Idempotencia

En la API de IA, `X-Operation-Id` es una clave de idempotencia. La primera respuesta exitosa a un id de operación se almacena durante 24 horas. Envía el mismo id de nuevo y recibes esa misma respuesta, marcada con `x-idempotent-replay: true`, sin que el trabajo se ejecute dos veces y sin un segundo cobro. Eso es lo que hace seguro un reintento tras un tiempo de espera agotado o una conexión interrumpida.

- Un **error nunca se almacena**: reintentar una llamada fallida con el mismo id la ejecuta de nuevo.
- Mientras la primera llamada **sigue en ejecución**, una segunda llamada con el mismo id es `409 operation_in_progress`; espera y reintenta.
- En chat, embeddings y run, un id reutilizado **devuelve la respuesta almacenada sin importar lo que diga el cuerpo nuevo**. En los elementos de colección, el mismo id con un archivo distinto es `409 operation_id_reused`.
- Genera un id nuevo para cada operación nueva, y reutiliza un id solo para reintentar la llamada para la que se creó.

Cada respuesta de IA repite el id con el que se ejecutó en `x-operation-id`; en un `GET` sin uno, el servicio inventa uno. Una respuesta de chat con streaming nunca se almacena, por lo que no se puede repetir.

## Ejemplo

```sh
export INOVACC_API_KEY="<your API key>"
```

**cURL**

```sh
curl "https://ai.inovacc.dev/v1/models" \
  -H "Authorization: Bearer $INOVACC_API_KEY"
```

**TypeScript**

```ts
const url = "https://ai.inovacc.dev/v1/models";

const response = await fetch(url, {
  method: "GET",
  headers: {
    Authorization: `Bearer ${process.env.INOVACC_API_KEY}`,
  },
});

console.log(response.status, await response.text());
```

**Python**

```py
import os
import urllib.request

request = urllib.request.Request(
    "https://ai.inovacc.dev/v1/models",
    method="GET",
    headers={
        "User-Agent": "inovacc-python-sample",
        "Authorization": "Bearer " + os.environ["INOVACC_API_KEY"],
    },
)

with urllib.request.urlopen(request) as response:
    print(response.status, response.read().decode())
```

**Go**

```go
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
)

const url = "https://ai.inovacc.dev/v1/models"

func main() {
	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		panic(err)
	}
	req.Header.Set("Authorization", "Bearer "+os.Getenv("INOVACC_API_KEY"))

	res, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()

	out, err := io.ReadAll(res.Body)
	if err != nil {
		panic(err)
	}
	fmt.Println(res.Status, string(out))
}
```

**Rust**

```rs
// Cargo.toml: reqwest = { version = "0.12", features = ["blocking", "json"] }

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let api_key = std::env::var("INOVACC_API_KEY")?;
    let response = reqwest::blocking::Client::new()
        .get("https://ai.inovacc.dev/v1/models")
        .bearer_auth(api_key)
        .send()?;
    println!("{} {}", response.status(), response.text()?);
    Ok(())
}
```

**JavaScript**

```mjs
const url = "https://ai.inovacc.dev/v1/models";

const response = await fetch(url, {
  method: "GET",
  headers: {
    Authorization: `Bearer ${process.env.INOVACC_API_KEY}`,
  },
});

console.log(response.status, await response.text());
```

**PHP**

```php
<?php

$curl = curl_init('https://ai.inovacc.dev/v1/models');
curl_setopt_array($curl, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('INOVACC_API_KEY'),
    ],
]);

$response = curl_exec($curl);
echo curl_getinfo($curl, CURLINFO_HTTP_CODE), ' ', $response, "\n";
curl_close($curl);
```

**Ruby**

```rb
require "net/http"
require "uri"

uri = URI('https://ai.inovacc.dev/v1/models')

request = Net::HTTP::Get.new(uri)
request["Authorization"] = "Bearer #{ENV.fetch('INOVACC_API_KEY')}"

response = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https") do |http|
  http.request(request)
end

puts "#{response.code} #{response.body}"
```

**Java**

```java
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

public class Main {
    public static void main(String[] args) throws Exception {
        String apiKey = System.getenv("INOVACC_API_KEY");

        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create("https://ai.inovacc.dev/v1/models"))
                .header("Authorization", "Bearer " + apiKey)
                .GET()
                .build();

        HttpResponse<String> response = HttpClient.newHttpClient()
                .send(request, HttpResponse.BodyHandlers.ofString());
        System.out.println(response.statusCode() + " " + response.body());
    }
}
```

**C#**

```cs
var url = "https://ai.inovacc.dev/v1/models";
var apiKey = Environment.GetEnvironmentVariable("INOVACC_API_KEY");

using var client = new HttpClient();
using var request = new HttpRequestMessage(HttpMethod.Get, url);
request.Headers.Add("Authorization", $"Bearer {apiKey}");

using var response = await client.SendAsync(request);
Console.WriteLine($"{(int)response.StatusCode} {await response.Content.ReadAsStringAsync()}");
```

**Kotlin**

```kt
import java.net.URI
import java.net.http.HttpClient
import java.net.http.HttpRequest
import java.net.http.HttpResponse

fun main() {
    val apiKey = System.getenv("INOVACC_API_KEY")

    val request = HttpRequest.newBuilder()
        .uri(URI.create("https://ai.inovacc.dev/v1/models"))
        .header("Authorization", "Bearer " + apiKey)
        .GET()
        .build()

    val response = HttpClient.newHttpClient()
        .send(request, HttpResponse.BodyHandlers.ofString())
    println("${response.statusCode()} ${response.body()}")
}
```

**Swift**

```swift
import Foundation
#if canImport(FoundationNetworking)
import FoundationNetworking
#endif

let apiKey = ProcessInfo.processInfo.environment["INOVACC_API_KEY"] ?? ""

let url = URL(string: "https://ai.inovacc.dev/v1/models")!
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.setValue("Bearer \(apiKey)", forHTTPHeaderField: "Authorization")

let (data, response) = try await URLSession.shared.data(for: request)
let status = (response as? HTTPURLResponse)?.statusCode ?? 0
print(status, String(decoding: data, as: UTF8.self))
```

La respuesta lista las rutas configuradas para tu organización. Las rutas se nombran por organización, así que la lista que ves es la tuya. Una llamada que cambia algo agrega su id de operación:

```sh
curl -X POST "https://ai.inovacc.dev/v1/chat/completions" \
  -H "Authorization: Bearer $INOVACC_API_KEY" \
  -H "Content-Type: application/json" \
  -H "X-Operation-Id: $(uuidgen)" \
  -d '{"model":"auto","messages":[{"role":"user","content":"Hello"}]}'
```

## Ciclo de vida de las claves

Una clave pasa por cuatro etapas, y cada una tiene sus propios cuidados.

1. **Creación.** Crea una clave por aplicación y por entorno (producción, staging, la laptop de un desarrollador), nunca una clave compartida por todo. Copia la clave directamente de la consola a tu almacén de secretos.
2. **Uso.** Carga la clave desde el entorno o un administrador de secretos al arrancar. Envíala solo en el encabezado `Authorization`, solo por HTTPS, y solo desde un servidor: una clave secreta que llega a la API de Datos o de Eventos desde un navegador (con un encabezado `Origin`) se rechaza con `403 secret_key_in_browser`.
3. **Rotación.** Crea una segunda clave, despliégala en todos los lugares donde se usa la primera, confirma en el [Registro de actividad](/es/products/activity/) que el id de la clave anterior ya no aparece como `actor_id`, y luego haz que se deshabilite la clave anterior. Una clave deshabilitada responde `403 key_disabled`; revocar la credencial de una aplicación surte efecto en menos de un minuto.
4. **Retiro.** Quita la clave de todo almacén de secretos y pipeline cuando una aplicación o un entorno desaparezca.

Un comportamiento que conviene prever: en la Vector Database, el ámbito `local` pertenece a la clave que lo escribió. Una clave nueva ve un ámbito `local` nuevo y vacío; usa el ámbito `shared` para el conocimiento que debe sobrevivir a una rotación.

Los tokens de acceso OAuth caducan. Cuando una llamada responde `401 invalid_credentials` con un token que antes funcionaba, obtén un token nuevo y reintenta una vez; no reintentes un `401` en un bucle.

## Higiene de las claves

- **Nunca subas una clave al repositorio**, ni siquiera a un repositorio privado o a un archivo de pruebas. Mantén los archivos `.env` fuera del control de versiones.
- **Nunca registres el encabezado `Authorization`**, y ocúltalo en los reportes de errores y en las trazas.
- **Da a cada integración su propia clave**, para que su tráfico sea visible en el Registro de actividad y pueda rotarse por separado.
- **Prefiere una clave vinculada a una aplicación** con solo las capacidades que necesita en lugar de una clave para toda la organización.
- **Rota según un calendario**, y de inmediato cuando se va alguien que tenía acceso o una clave pudo haberse filtrado.
- **En un navegador, usa una clave publicable o el token de una persona con sesión iniciada**, y cierra toda colección con reglas.

## Ver también

- [Errores](/es/guides/errors/): el envoltorio de errores y los códigos que puedes manejar.
- [Límites](/es/guides/limits/): tamaños de solicitud, tiempos de espera y tasas.
- [Webhooks](/es/guides/webhooks/): verificar entregas con un secreto de firma.
- [Referencia de la API](/es/reference/): cada producto y sus endpoints.
