Embeddings
Turn text into vectors for search, clustering and similarity — 1024 dimensions, one call for a batch.
POST /v1/embeddings turns text into a fixed-length vector — the numeric form most search, clustering and recommendation code actually wants. It's served by BAAI's bge-m3, running on the same GPU as everything else.
/v1/embeddings| Parameter | Type | Description |
|---|---|---|
| input Required | string | string[] | The text to embed. A single string or an array — sending an array is one request, one round trip, and the natural way to embed a batch. |
| model Optional | string | The only embedding model this deployment serves. Default: larsa-embed |
| encoding_format Optional | "float" | "base64" | How each vector is encoded in the response. base64 is smaller on the wire; float is a plain JSON array of numbers.Default: float |
curl -s https://api.console.larsa.larsima.com/v1/embeddings \
-H "Authorization: Bearer $LARSA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "larsa-embed",
"input": ["Notice of termination must be given in writing.",
"El aviso de terminación debe darse por escrito."]
}'
import os
from openai import OpenAI
client = OpenAI(base_url="https://api.console.larsa.larsima.com/v1",
api_key=os.environ["LARSA_API_KEY"])
resp = client.embeddings.create(
model="larsa-embed",
input=["Notice of termination must be given in writing.",
"El aviso de terminación debe darse por escrito."],
)
for d in resp.data:
print(d.index, len(d.embedding))
print(resp.usage)
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://api.console.larsa.larsima.com/v1",
apiKey: process.env.LARSA_API_KEY,
});
const resp = await client.embeddings.create({
model: "larsa-embed",
input: [
"Notice of termination must be given in writing.",
"El aviso de terminación debe darse por escrito.",
],
});
for (const d of resp.data) console.log(d.index, d.embedding.length);
console.log(resp.usage);
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://api.console.larsa.larsima.com/v1",
apiKey: process.env.LARSA_API_KEY,
});
const resp = await client.embeddings.create({
model: "larsa-embed",
input: [
"Notice of termination must be given in writing.",
"El aviso de terminación debe darse por escrito.",
],
});
const dims: number[] = resp.data.map((d) => d.embedding.length);
console.log(dims, resp.usage);
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"os"
)
func main() {
body, _ := json.Marshal(map[string]any{
"model": "larsa-embed",
"input": []string{
"Notice of termination must be given in writing.",
"El aviso de terminación debe darse por escrito.",
},
})
req, _ := http.NewRequest("POST",
"https://api.console.larsa.larsima.com/v1/embeddings", bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+os.Getenv("LARSA_API_KEY"))
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
var out struct {
Data []struct {
Index int `json:"index"`
Embedding []float64 `json:"embedding"`
} `json:"data"`
Usage struct {
TotalTokens int `json:"total_tokens"`
} `json:"usage"`
}
json.NewDecoder(resp.Body).Decode(&out)
for _, d := range out.Data {
fmt.Println(d.Index, len(d.Embedding))
}
fmt.Println("total_tokens:", out.Usage.TotalTokens)
}
// Cargo.toml: reqwest = { version = "0.12", features = ["json"] }
// serde = { version = "1", features = ["derive"] }
// serde_json = "1", tokio = { version = "1", features = ["full"] }
use serde::Deserialize;
use serde_json::json;
use std::env;
#[derive(Deserialize)]
struct EmbedItem { index: u32, embedding: Vec<f64> }
#[derive(Deserialize)]
struct Usage { total_tokens: u32 }
#[derive(Deserialize)]
struct EmbedResp { data: Vec<EmbedItem>, usage: Usage }
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let key = env::var("LARSA_API_KEY")?;
let client = reqwest::Client::new();
let resp: EmbedResp = client
.post("https://api.console.larsa.larsima.com/v1/embeddings")
.bearer_auth(key)
.json(&json!({
"model": "larsa-embed",
"input": [
"Notice of termination must be given in writing.",
"El aviso de terminación debe darse por escrito."
]
}))
.send()
.await?
.json()
.await?;
for item in &resp.data {
println!("{} {}", item.index, item.embedding.len());
}
println!("total_tokens: {}", resp.usage.total_tokens);
Ok(())
}
// Maven: org.json:json
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import org.json.JSONArray;
import org.json.JSONObject;
public class Embeddings {
public static void main(String[] args) throws Exception {
JSONObject body = new JSONObject()
.put("model", "larsa-embed")
.put("input", new JSONArray(java.util.List.of(
"Notice of termination must be given in writing.",
"El aviso de terminación debe darse por escrito.")));
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.console.larsa.larsima.com/v1/embeddings"))
.header("Authorization", "Bearer " + System.getenv("LARSA_API_KEY"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body.toString()))
.build();
HttpResponse<String> response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());
JSONObject out = new JSONObject(response.body());
JSONArray data = out.getJSONArray("data");
for (int i = 0; i < data.length(); i++) {
JSONObject d = data.getJSONObject(i);
System.out.println(d.getInt("index") + " " + d.getJSONArray("embedding").length());
}
System.out.println("total_tokens: " + out.getJSONObject("usage").getInt("total_tokens"));
}
}
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
var payload = new {
model = "larsa-embed",
input = new[] {
"Notice of termination must be given in writing.",
"El aviso de terminación debe darse por escrito.",
},
};
using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", Environment.GetEnvironmentVariable("LARSA_API_KEY"));
var content = new StringContent(JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json");
var response = await client.PostAsync("https://api.console.larsa.larsima.com/v1/embeddings", content);
var json = JsonDocument.Parse(await response.Content.ReadAsStringAsync()).RootElement;
foreach (var d in json.GetProperty("data").EnumerateArray())
Console.WriteLine($"{d.GetProperty("index")} {d.GetProperty("embedding").GetArrayLength()}");
Console.WriteLine($"total_tokens: {json.GetProperty("usage").GetProperty("total_tokens")}");
<?php
$payload = [
"model" => "larsa-embed",
"input" => [
"Notice of termination must be given in writing.",
"El aviso de terminación debe darse por escrito.",
],
];
$ch = curl_init("https://api.console.larsa.larsima.com/v1/embeddings");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer " . getenv("LARSA_API_KEY"),
"Content-Type: application/json",
],
CURLOPT_POSTFIELDS => json_encode($payload),
]);
$out = json_decode(curl_exec($ch), true);
foreach ($out["data"] as $d) {
echo $d["index"] . " " . count($d["embedding"]) . "\n";
}
echo "total_tokens: " . $out["usage"]["total_tokens"] . "\n";
require "net/http"
require "json"
require "uri"
uri = URI("https://api.console.larsa.larsima.com/v1/embeddings")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Post.new(uri)
request["Authorization"] = "Bearer #{ENV.fetch('LARSA_API_KEY')}"
request["Content-Type"] = "application/json"
request.body = {
model: "larsa-embed",
input: [
"Notice of termination must be given in writing.",
"El aviso de terminación debe darse por escrito.",
],
}.to_json
out = JSON.parse(http.request(request).body)
out["data"].each { |d| puts "#{d['index']} #{d['embedding'].length}" }
puts "total_tokens: #{out['usage']['total_tokens']}"
1024 dimensions, already normalised
Every vector bge-m3 returns has exactly 1024 numbers, and it's already unit-length — the vector's own magnitude is 1.0. That means cosine similarity and a plain dot product give you the same ranking, so most similarity code can skip the normalisation step entirely.
Batching and the token ceiling
Send up to a few hundred short passages as one input array — they're processed in a single call and usage.total_tokens counts the whole batch. The server's context is shared across every item in the request, capped at 8192 tokens total; go over it and you get the server's own message back, unfiltered:
{"error":{"code":500,"message":"input (9002 tokens) is too large to process. increase the physical batch size (current batch size: 8192)","type":"server_error"}}There's no batch-count limit as such — 50 short sentences in one call cost 500 tokens and came back in one round trip in testing. The ceiling that matters is the sum of every item's token count, not how many items there are.
Worked example: similarity search
Embed a small corpus once, embed the query, rank by dot product — no vector database required for a handful of documents.
# One request embeds the corpus and the query together;
# ranking happens client-side (see the Python/JS tabs) since bge-m3's
# vectors are already unit length -- a dot product is the cosine score.
curl -s https://api.console.larsa.larsima.com/v1/embeddings \
-H "Authorization: Bearer $LARSA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "larsa-embed",
"input": [
"The tenant may terminate the lease with thirty days written notice.",
"An appeal must be filed within twenty days of the judgment.",
"The buyer is entitled to a refund if the goods are defective.",
"Interest on the unpaid balance accrues at the statutory rate.",
"How many days do I have to appeal a court decision?"
]
}'
import os
from openai import OpenAI
client = OpenAI(base_url="https://api.console.larsa.larsima.com/v1",
api_key=os.environ["LARSA_API_KEY"])
corpus = [
"The tenant may terminate the lease with thirty days' written notice.",
"An appeal must be filed within twenty days of the judgment.",
"The buyer is entitled to a refund if the goods are defective.",
"Interest on the unpaid balance accrues at the statutory rate.",
]
query = "How many days do I have to appeal a court decision?"
resp = client.embeddings.create(model="larsa-embed", input=corpus + [query])
*corpus_vecs, query_vec = [d.embedding for d in resp.data]
def dot(a, b):
return sum(x * y for x, y in zip(a, b))
ranked = sorted(zip(corpus, corpus_vecs),
key=lambda cv: dot(cv[1], query_vec), reverse=True)
for text, vec in ranked:
print(f"{dot(vec, query_vec):.4f} {text}")
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://api.console.larsa.larsima.com/v1",
apiKey: process.env.LARSA_API_KEY,
});
const corpus = [
"The tenant may terminate the lease with thirty days' written notice.",
"An appeal must be filed within twenty days of the judgment.",
"The buyer is entitled to a refund if the goods are defective.",
"Interest on the unpaid balance accrues at the statutory rate.",
];
const query = "How many days do I have to appeal a court decision?";
const resp = await client.embeddings.create({
model: "larsa-embed",
input: [...corpus, query],
});
const vectors = resp.data.map((d) => d.embedding);
const queryVec = vectors[vectors.length - 1];
const corpusVecs = vectors.slice(0, -1);
const dot = (a, b) => a.reduce((sum, x, i) => sum + x * b[i], 0);
const ranked = corpus
.map((text, i) => ({ text, score: dot(corpusVecs[i], queryVec) }))
.sort((a, b) => b.score - a.score);
for (const { text, score } of ranked) console.log(score.toFixed(4), text);
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://api.console.larsa.larsima.com/v1",
apiKey: process.env.LARSA_API_KEY,
});
const corpus: string[] = [
"The tenant may terminate the lease with thirty days' written notice.",
"An appeal must be filed within twenty days of the judgment.",
"The buyer is entitled to a refund if the goods are defective.",
"Interest on the unpaid balance accrues at the statutory rate.",
];
const query = "How many days do I have to appeal a court decision?";
const resp = await client.embeddings.create({
model: "larsa-embed",
input: [...corpus, query],
});
const vectors: number[][] = resp.data.map((d) => d.embedding);
const queryVec = vectors[vectors.length - 1];
const corpusVecs = vectors.slice(0, -1);
const dot = (a: number[], b: number[]) =>
a.reduce((sum, x, i) => sum + x * b[i], 0);
const ranked = corpus
.map((text, i) => ({ text, score: dot(corpusVecs[i], queryVec) }))
.sort((a, b) => b.score - a.score);
for (const { text, score } of ranked) console.log(score.toFixed(4), text);
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"os"
"sort"
)
type embedResp struct {
Data []struct {
Embedding []float64 `json:"embedding"`
} `json:"data"`
}
func dot(a, b []float64) float64 {
var s float64
for i := range a {
s += a[i] * b[i]
}
return s
}
func main() {
corpus := []string{
"The tenant may terminate the lease with thirty days' written notice.",
"An appeal must be filed within twenty days of the judgment.",
"The buyer is entitled to a refund if the goods are defective.",
"Interest on the unpaid balance accrues at the statutory rate.",
}
query := "How many days do I have to appeal a court decision?"
body, _ := json.Marshal(map[string]any{
"model": "larsa-embed",
"input": append(append([]string{}, corpus...), query),
})
req, _ := http.NewRequest("POST",
"https://api.console.larsa.larsima.com/v1/embeddings", bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+os.Getenv("LARSA_API_KEY"))
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
var out embedResp
json.NewDecoder(resp.Body).Decode(&out)
queryVec := out.Data[len(out.Data)-1].Embedding
type scored struct {
text string
score float64
}
var ranked []scored
for i, text := range corpus {
ranked = append(ranked, scored{text, dot(out.Data[i].Embedding, queryVec)})
}
sort.Slice(ranked, func(i, j int) bool { return ranked[i].score > ranked[j].score })
for _, r := range ranked {
fmt.Printf("%.4f %s\n", r.score, r.text)
}
}
// Cargo.toml: reqwest = { version = "0.12", features = ["json"] }
// serde = { version = "1", features = ["derive"] }
// serde_json = "1", tokio = { version = "1", features = ["full"] }
use serde::Deserialize;
use serde_json::json;
use std::env;
#[derive(Deserialize)]
struct EmbedItem { embedding: Vec<f64> }
#[derive(Deserialize)]
struct EmbedResp { data: Vec<EmbedItem> }
fn dot(a: &[f64], b: &[f64]) -> f64 {
a.iter().zip(b).map(|(x, y)| x * y).sum()
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let corpus = vec![
"The tenant may terminate the lease with thirty days' written notice.",
"An appeal must be filed within twenty days of the judgment.",
"The buyer is entitled to a refund if the goods are defective.",
"Interest on the unpaid balance accrues at the statutory rate.",
];
let query = "How many days do I have to appeal a court decision?";
let key = env::var("LARSA_API_KEY")?;
let mut input: Vec<&str> = corpus.clone();
input.push(query);
let client = reqwest::Client::new();
let resp: EmbedResp = client
.post("https://api.console.larsa.larsima.com/v1/embeddings")
.bearer_auth(key)
.json(&json!({ "model": "larsa-embed", "input": input }))
.send()
.await?
.json()
.await?;
let query_vec = &resp.data.last().unwrap().embedding;
let mut ranked: Vec<(&str, f64)> = corpus
.iter()
.enumerate()
.map(|(i, &t)| (t, dot(&resp.data[i].embedding, query_vec)))
.collect();
ranked.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap());
for (text, score) in ranked {
println!("{score:.4} {text}");
}
Ok(())
}
// Maven: org.json:json
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.*;
import org.json.JSONArray;
import org.json.JSONObject;
public class SimilaritySearch {
static double dot(JSONArray a, JSONArray b) {
double s = 0;
for (int i = 0; i < a.length(); i++) s += a.getDouble(i) * b.getDouble(i);
return s;
}
public static void main(String[] args) throws Exception {
List<String> corpus = List.of(
"The tenant may terminate the lease with thirty days' written notice.",
"An appeal must be filed within twenty days of the judgment.",
"The buyer is entitled to a refund if the goods are defective.",
"Interest on the unpaid balance accrues at the statutory rate.");
String query = "How many days do I have to appeal a court decision?";
List<String> input = new ArrayList<>(corpus);
input.add(query);
JSONObject body = new JSONObject()
.put("model", "larsa-embed")
.put("input", input);
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.console.larsa.larsima.com/v1/embeddings"))
.header("Authorization", "Bearer " + System.getenv("LARSA_API_KEY"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body.toString()))
.build();
HttpResponse<String> response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());
JSONArray data = new JSONObject(response.body()).getJSONArray("data");
JSONArray queryVec = data.getJSONObject(data.length() - 1).getJSONArray("embedding");
List<Map.Entry<String, Double>> ranked = new ArrayList<>();
for (int i = 0; i < corpus.size(); i++) {
JSONArray vec = data.getJSONObject(i).getJSONArray("embedding");
ranked.add(Map.entry(corpus.get(i), dot(vec, queryVec)));
}
ranked.sort((a, b) -> Double.compare(b.getValue(), a.getValue()));
for (var r : ranked) {
System.out.printf("%.4f %s%n", r.getValue(), r.getKey());
}
}
}
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
var corpus = new[] {
"The tenant may terminate the lease with thirty days' written notice.",
"An appeal must be filed within twenty days of the judgment.",
"The buyer is entitled to a refund if the goods are defective.",
"Interest on the unpaid balance accrues at the statutory rate.",
};
var query = "How many days do I have to appeal a court decision?";
var payload = new { model = "larsa-embed", input = corpus.Append(query).ToArray() };
using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", Environment.GetEnvironmentVariable("LARSA_API_KEY"));
var content = new StringContent(JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json");
var response = await client.PostAsync("https://api.console.larsa.larsima.com/v1/embeddings", content);
var json = JsonDocument.Parse(await response.Content.ReadAsStringAsync());
var data = json.RootElement.GetProperty("data");
double[] Vec(int i) => data[i].GetProperty("embedding").EnumerateArray().Select(e => e.GetDouble()).ToArray();
double Dot(double[] a, double[] b) => a.Zip(b, (x, y) => x * y).Sum();
var queryVec = Vec(data.GetArrayLength() - 1);
var ranked = corpus
.Select((text, i) => (text, score: Dot(Vec(i), queryVec)))
.OrderByDescending(r => r.score);
foreach (var (text, score) in ranked)
Console.WriteLine($"{score:F4} {text}");
<?php
$corpus = [
"The tenant may terminate the lease with thirty days' written notice.",
"An appeal must be filed within twenty days of the judgment.",
"The buyer is entitled to a refund if the goods are defective.",
"Interest on the unpaid balance accrues at the statutory rate.",
];
$query = "How many days do I have to appeal a court decision?";
$payload = ["model" => "larsa-embed", "input" => [...$corpus, $query]];
$ch = curl_init("https://api.console.larsa.larsima.com/v1/embeddings");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer " . getenv("LARSA_API_KEY"),
"Content-Type: application/json",
],
CURLOPT_POSTFIELDS => json_encode($payload),
]);
$data = json_decode(curl_exec($ch), true)["data"];
function dot($a, $b) {
$s = 0;
foreach ($a as $i => $v) $s += $v * $b[$i];
return $s;
}
$queryVec = end($data)["embedding"];
$ranked = [];
foreach ($corpus as $i => $text) {
$ranked[] = [$text, dot($data[$i]["embedding"], $queryVec)];
}
usort($ranked, fn($a, $b) => $b[1] <=> $a[1]);
foreach ($ranked as [$text, $score]) {
printf("%.4f %s\n", $score, $text);
}
require "net/http"
require "json"
require "uri"
corpus = [
"The tenant may terminate the lease with thirty days' written notice.",
"An appeal must be filed within twenty days of the judgment.",
"The buyer is entitled to a refund if the goods are defective.",
"Interest on the unpaid balance accrues at the statutory rate.",
]
query = "How many days do I have to appeal a court decision?"
uri = URI("https://api.console.larsa.larsima.com/v1/embeddings")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Post.new(uri)
request["Authorization"] = "Bearer #{ENV.fetch('LARSA_API_KEY')}"
request["Content-Type"] = "application/json"
request.body = { model: "larsa-embed", input: corpus + [query] }.to_json
data = JSON.parse(http.request(request).body)["data"]
def dot(a, b)
a.zip(b).sum { |x, y| x * y }
end
query_vec = data.last["embedding"]
ranked = corpus.each_with_index.map { |text, i|
[text, dot(data[i]["embedding"], query_vec)]
}.sort_by { |_, score| -score }
ranked.each { |text, score| puts format("%.4f %s", score, text) }