Inovacc Desarrolladores

Referencia de la API

Archivos

Almacenamiento de archivos por contenido, con carga por partes para archivos grandes.

URL base https://data.inovacc.dev

Descripción general

Archivos almacena el contenido binario de tu aplicación (imágenes, documentos, exportaciones, grabaciones) en https://data.inovacc.dev, con cargas por partes para todo lo que sea grande. Ofrece dos maneras de direccionar un archivo:

  • por ruta, en la carpeta de tu aplicación: PUT /v1/files/users/123/avatar.png, como un bucket de almacenamiento en la nube nombra los archivos, con listados por carpeta, metadatos de usuario y carpetas compartidas con otras aplicaciones de tu espacio de trabajo;
  • por contenido, dentro de una base de datos: PUT /v1/databases/{db}/blobs/{sha256}, donde el propio SHA-256 del archivo es su nombre, de modo que un registro de Datos puede apuntar a él con un campo blob.

El problema que resuelve es mantener los archivos junto a tus datos sin operar almacenamiento: Inovacc elige dónde viven los bytes, los verifica contra su hash, elimina duplicados dentro de tu carpeta y los devuelve con encabezados que impiden que un archivo descargado se ejecute en un navegador.

Usa Archivos para todo lo que sean bytes en lugar de campos. Conserva la descripción estructurada de un archivo (propietario, título, estado) como un registro en Datos. Para hacer que un archivo se pueda buscar por significado, agrégalo a una colección de Conocimiento y búsqueda vectorial.

Conceptos

La carpeta de tu aplicación. Cada aplicación tiene su propia carpeta. La carpeta se elige a partir de la credencial, nunca de la solicitud: una credencial vinculada a una aplicación usa la carpeta de esa aplicación, y una credencial no vinculada a ninguna aplicación recibe 403 application_required en toda ruta de rutas de archivo. Las rutas son UTF-8, de 1 a 1,024 bytes, separadas por /, sensibles a mayúsculas y minúsculas, sin ningún segmento vacío, . ni ..; una ruta que no esté ya en esa forma se rechaza, nunca se reescribe en silencio.

Metadatos. Una escritura puede llevar un Content-Type y tus propios encabezados X-File-Meta-<name> (2 KiB en total). Ambos regresan en cada lectura. Una escritura reemplaza por completo el contenido, el tipo y los metadatos del archivo.

Hashes. Cada archivo se identifica por su SHA-256. Envía X-File-Sha256 con una escritura y los bytes se verifican contra él (400 hash_mismatch, no se almacena nada). Dentro de la carpeta de una aplicación, dos rutas con los mismos bytes comparten una sola copia almacenada; la copia se conserva hasta que se va la última ruta. La deduplicación nunca cruza aplicaciones ni organizaciones.

Blobs de bases de datos. Un blob se direcciona por el SHA-256 en su ruta dentro de una base de datos. Cargarlo de nuevo responde 200 en lugar de 201. Cualquiera con permiso de lectura sobre la base de datos que conozca el hash puede leerlo: las reglas de registros no se aplican a los blobs.

Carpetas compartidas. Una aplicación puede crear una carpeta team que aparece para toda aplicación con acceso como shared/team/.... El creador es su propietario y concede a otras aplicaciones del mismo espacio de trabajo read o read_write. Una aplicación sin acceso no puede saber que la carpeta existe: toda operación responde 404.

Archivos grandes. Hasta 100 MiB van en una sola solicitud. Por encima de eso, hasta 5 GiB, cargas por partes y luego vinculas el resultado a una ruta (o se convierte en el blob).

Cómo funciona

Toda llamada lleva Authorization: Bearer <credential>; no hay encabezado de organización, porque tu organización y tu aplicación se obtienen de la credencial. El permiso que se verifica es lectura, escritura o eliminación de blobs. Una página web puede usar estas rutas con una clave publicable desde un origen que su aplicación liste.

Escribir por ruta. PUT /v1/files/{path} con los bytes y un Content-Length (obligatorio). La respuesta es 201 para una ruta nueva o 200 para un reemplazo: {"path","size","sha256","content_type","updated_at","metadata"} y ETag: "<sha256>".

Leer. GET /v1/files/{path} devuelve el archivo completo (no se admiten rangos) con Content-Type, ETag, X-File-Sha256, X-File-Updated-At y tus encabezados X-File-Meta-*. Cada descarga también lleva X-Content-Type-Options: nosniff, una Content-Security-Policy que la aísla y Cache-Control: no-store, de modo que una página HTML cargada nunca pueda ejecutarse como tu sitio. HEAD devuelve solo los encabezados.

Listar. GET /v1/files?prefix=photos/&delimiter=/ lista los archivos directamente bajo una carpeta como items y cada subcarpeta una vez en prefixes. Sigue next_cursor hasta que sea null: una página puede ser más corta que limit y aun así tener una siguiente.

Por partes. POST /v1/files-uploads/{sha256} inicia una carga (o responde 200 con exists: true cuando tu carpeta ya contiene esos bytes). Envía las partes de la 1 a la 10,000 con PUT .../{upload_id}/parts/{n}, cada una de entre 5 MiB y 100 MiB excepto la última, y luego POST .../complete con la lista de partes y sus etag. Inovacc vuelve a leer el objeto completo y verifica su SHA-256 y su tamaño. Por último, PUT /v1/files/{path} con X-File-Sha256 y un cuerpo vacío le da una ruta. Los blobs de bases de datos usan los mismos cuatro pasos bajo /v1/databases/{db}/blobs/{sha256}/uploads.

Las cargas, descargas y eliminaciones se registran en el Registro de actividad con el hash y el tamaño del archivo.

Primeros pasos

Necesitas una clave secreta vinculada a una aplicación, con permisos de blobs (Autenticación).

  1. Carga un archivo. PUT /v1/files/reports/2026-10.csv con el archivo como cuerpo y Content-Type: text/csv (consulta los ejemplos). La respuesta es 201 con su sha256.
  2. Léelo de vuelta. GET /v1/files/reports/2026-10.csv. Los bytes llegan con ETag igual al hash del paso 1.
  3. Lista la carpeta. GET /v1/files?prefix=reports/&delimiter=/. Tu archivo está en items.
  4. Reemplázalo. Haz PUT en la misma ruta con bytes nuevos. La respuesta es 200, con un nuevo sha256.
  5. Elimínalo. DELETE /v1/files/reports/2026-10.csv responde 204; un segundo GET es 404 file_not_found.

Casos de uso

Fotos de perfil desde el navegador. Una aplicación web carga cada avatar en users/<id>/avatar.png con una clave publicable y guarda el sha256 devuelto en el registro del usuario, para que una página pueda saber cuándo cambió la foto.

Exportaciones compartidas con una aplicación asociada. Una aplicación de reportes crea la carpeta compartida exports, concede read a la aplicación de facturación, y escribe archivos mensuales bajo shared/exports/. La aplicación de facturación los lista y los descarga; revocar la concesión surte efecto en su siguiente solicitud.

Medios grandes con integridad. Una herramienta de video calcula el hash de una grabación de 2 GiB en el cliente, la carga en partes de 100 MiB y la completa. Inovacc la almacena solo si el SHA-256 coincide, de modo que una transferencia corrupta nunca se convierte en un archivo.

Límites y precios

LímiteValor
Archivo o blob en una sola solicitud100 MiB, Content-Length obligatorio
Archivo o blob cargado por partes5 GiB; partes de 5 MiB a 100 MiB (excepto la última), numeradas de 1 a 10,000
Ruta1,024 bytes
Prefijo de listado256 bytes
Metadatos por archivo2 KiB
Página de listado1 a 200 (por defecto 50)
Nombre de carpeta compartida1 a 64 letras, dígitos, _ o -
Tasa600 solicitudes por minuto por titular de credencial, por ubicación

Precios: a consultar.

Errores

EstadoCódigoQué significa y qué hacer
400invalid_path, invalid_prefixLa ruta o el prefijo no está en forma canónica; corrígelo de tu lado.
400invalid_metadataContent-Type o X-File-Meta-* incumple las reglas o supera 2 KiB.
400hash_mismatchLos bytes no coinciden con el SHA-256 que enviaste; no se almacenó nada.
400invalid_part, invalid_limit, invalid_cursor, invalid_granteeCorrige las partes de la carga, el listado o la concesión.
401invalid_credentialsEnvía una credencial válida.
403application_requiredLas rutas de archivo necesitan una credencial vinculada a una aplicación.
403forbiddenFalta un permiso, o un beneficiario de read intentó escribir.
404file_not_found, blob_not_found, share_not_found, upload_not_foundNo existe, o no tienes acceso a él.
409already_exists, not_emptyEl nombre de la carpeta compartida ya está en uso; vacía la carpeta antes de eliminarla.
411length_requiredEnvía Content-Length.
413payload_too_largeMás de 100 MiB en una solicitud (usa partes) o más de 5 GiB.
429rate_limitedReduce el ritmo.
503service_unavailableReintenta más tarde.

Buenas prácticas

  • Envía X-File-Sha256 en cada escritura para que una carga dañada se rechace en lugar de almacenarse.
  • Usa partes por encima de 100 MiB, y comienza con POST .../uploads: responde exists: true cuando los bytes ya están ahí.
  • Guarda el sha256, no solo la ruta, en tus registros; te dice exactamente a qué contenido se refiere un registro.
  • Sigue next_cursor hasta el final al listar; las páginas cortas son normales.
  • Concede read a menos que un asociado deba escribir, y revoca las concesiones que ya no necesites.
  • No dependas de que los blobs sean privados por regla: cualquiera que pueda leer la base de datos y conozca el hash puede leer un blob.

Autenticación

Toda llamada lleva tu clave; tu organización viene de ella. Consulta la guía de autenticación.

EncabezadoAuthorizationBearer <clave de API>

Endpoints

PUT /v1/databases/{database}/blobs/{sha256}

GET /v1/databases/{database}/blobs/{sha256}

DELETE /v1/databases/{database}/blobs/{sha256}

POST /v1/databases/{database}/blobs/{sha256}/uploads

Ejemplo

Lenguaje

⋮
PUT /v1/databases/{database}/blobs/{sha256}Ejemplo

cURL

# The body is {} here: see parameters.
curl -X PUT "https://data.inovacc.dev/v1/databases/{database}/blobs/{sha256}" \
  -H "Authorization: Bearer $INOVACC_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{}'

TypeScript

// The body is {} here: see parameters.
const body: Record<string, unknown> = {};

const url = "https://data.inovacc.dev/v1/databases/{database}/blobs/{sha256}";

const response = await fetch(url, {
  method: "PUT",
  headers: {
    Authorization: `Bearer ${process.env.INOVACC_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify(body),
});

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

Python

import json
import os
import urllib.request

# The body is {} here: see parameters.
body = {}

request = urllib.request.Request(
    "https://data.inovacc.dev/v1/databases/{database}/blobs/{sha256}",
    data=json.dumps(body).encode(),
    method="PUT",
    headers={
        "User-Agent": "inovacc-python-sample",
        "Authorization": "Bearer " + os.environ["INOVACC_API_KEY"],
        "Content-Type": "application/json",
    },
)

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

Go

package main

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

const url = "https://data.inovacc.dev/v1/databases/{database}/blobs/{sha256}"

// The body is {} here: see parameters.
const body = `{}`

func main() {
	req, err := http.NewRequest("PUT", url, strings.NewReader(body))
	if err != nil {
		panic(err)
	}
	req.Header.Set("Authorization", "Bearer "+os.Getenv("INOVACC_API_KEY"))
	req.Header.Set("Content-Type", "application/json")

	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

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

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let api_key = std::env::var("INOVACC_API_KEY")?;
    // The body is {} here: see parameters.
    let body = serde_json::json!({});
    let response = reqwest::blocking::Client::new()
        .put("https://data.inovacc.dev/v1/databases/{database}/blobs/{sha256}")
        .bearer_auth(api_key)
        .json(&body)
        .send()?;
    println!("{} {}", response.status(), response.text()?);
    Ok(())
}

JavaScript

// The body is {} here: see parameters.
const body = {};

const url = "https://data.inovacc.dev/v1/databases/{database}/blobs/{sha256}";

const response = await fetch(url, {
  method: "PUT",
  headers: {
    Authorization: `Bearer ${process.env.INOVACC_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify(body),
});

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

PHP

<?php

// The body is {} here: see parameters.
$body = <<<'JSON'
{}
JSON;

$curl = curl_init('https://data.inovacc.dev/v1/databases/{database}/blobs/{sha256}');
curl_setopt_array($curl, [
    CURLOPT_CUSTOMREQUEST => 'PUT',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('INOVACC_API_KEY'),
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => $body,
]);

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

Ruby

require "net/http"
require "uri"

uri = URI('https://data.inovacc.dev/v1/databases/{database}/blobs/{sha256}')
# The body is {} here: see parameters.
body = <<~'JSON'
{}
JSON

request = Net::HTTP::Put.new(uri)
request["Authorization"] = "Bearer #{ENV.fetch('INOVACC_API_KEY')}"
request["Content-Type"] = "application/json"
request.body = body

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

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");
        // The body is {} here: see parameters.
        String body = "{}";

        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create("https://data.inovacc.dev/v1/databases/{database}/blobs/{sha256}"))
                .header("Authorization", "Bearer " + apiKey)
                .header("Content-Type", "application/json")
                .method("PUT", HttpRequest.BodyPublishers.ofString(body))
                .build();

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

C#

using System.Text;

// The body is {} here: see parameters.
var body = "{}";

var url = "https://data.inovacc.dev/v1/databases/{database}/blobs/{sha256}";
var apiKey = Environment.GetEnvironmentVariable("INOVACC_API_KEY");

using var client = new HttpClient();
using var request = new HttpRequestMessage(HttpMethod.Put, url);
request.Headers.Add("Authorization", $"Bearer {apiKey}");
request.Content = new StringContent(body, Encoding.UTF8, "application/json");

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

Kotlin

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")
    // The body is {} here: see parameters.
    val body = "{}"

    val request = HttpRequest.newBuilder()
        .uri(URI.create("https://data.inovacc.dev/v1/databases/{database}/blobs/{sha256}"))
        .header("Authorization", "Bearer " + apiKey)
        .header("Content-Type", "application/json")
        .method("PUT", HttpRequest.BodyPublishers.ofString(body))
        .build()

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

Swift

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

// The body is {} here: see parameters.
let body = #"""
{}
"""#

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

let url = URL(string: "https://data.inovacc.dev/v1/databases/{database}/blobs/{sha256}")!
var request = URLRequest(url: url)
request.httpMethod = "PUT"
request.setValue("Bearer \(apiKey)", forHTTPHeaderField: "Authorization")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = Data(body.utf8)

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))
Detalles de la fuente

Entrada del catálogo

Id
identity/component-taxonomy/capabilities#files
Repositorio
identity
Ruta
contracts/component-taxonomy/catalog/capabilities.json
Commit
08ef3cd75d82

Ejemplo

Id
identity/component-taxonomy/quickstart#files
Repositorio
identity
Ruta
contracts/component-taxonomy/catalog/capabilities.json
Commit
08ef3cd75d82
Actualizado el 2026-10-10.