Replicurve HTTP API

OpenAPI specification for the replication engine. Regenerated on every deployment so it always matches the running service.

Getting started

All endpoints live under /v1. Send JSON bodies with Content-Type: application/json and authenticate with a Bearer token. Request a key from the pricing page.

Base URL

https://replicurve.com/v1

Authentication

Include your API key on every request:

Authorization: Bearer <your-api-key>

Quick example

Submit aligned daily return series for the target. On Starter plans, basket is a preset universe name (string). On Professional plans and above, basket is return series keyed by ticker. Tune config.max_allocation and config.rebalance_freq as needed.

Custom basket (Professional+)

import os

import requests

response = requests.post(
    "https://replicurve.com/v1/replication",
    headers={"Authorization": f"Bearer {os.environ['API_KEY']}"},
    json={
        "target": {
            "returns": [
                {"date": "2024-01-02", "value": 0.0012},
                {"date": "2024-01-03", "value": -0.0008},
            ]
        },
        "basket": {
            "AAPL": [
                {"date": "2024-01-02", "value": 0.0021},
                {"date": "2024-01-03", "value": -0.0015},
            ]
        },
        "config": {"max_allocation": 10, "rebalance_freq": "weekly"},
    },
)
response.raise_for_status()
print(response.json())
const response = await fetch("https://replicurve.com/v1/replication", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    target: {
      returns: [
        { date: "2024-01-02", value: 0.0012 },
        { date: "2024-01-03", value: -0.0008 },
      ],
    },
    basket: {
      AAPL: [
        { date: "2024-01-02", value: 0.0021 },
        { date: "2024-01-03", value: -0.0015 },
      ],
    },
    config: { max_allocation: 10, rebalance_freq: "weekly" },
  }),
});

if (!response.ok) throw new Error(await response.text());
console.log(await response.json());
#include <curl/curl.h>
#include <cstdlib>
#include <iostream>
#include <string>

int main() {
    const char* url = "https://replicurve.com/v1/replication";
    const char* api_key = std::getenv("API_KEY");
    const std::string body = R"({
  "target": {
    "returns": [
      {"date": "2024-01-02", "value": 0.0012},
      {"date": "2024-01-03", "value": -0.0008}
    ]
  },
  "basket": {
    "AAPL": [
      {"date": "2024-01-02", "value": 0.0021},
      {"date": "2024-01-03", "value": -0.0015}
    ]
  },
  "config": {"max_allocation": 10, "rebalance_freq": "weekly"}
})";

    CURL* curl = curl_easy_init();
    struct curl_slist* headers = nullptr;
    headers = curl_slist_append(
        headers, ("Authorization: Bearer " + std::string(api_key)).c_str());
    headers = curl_slist_append(headers, "Content-Type: application/json");

    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body.c_str());

    CURLcode res = curl_easy_perform(curl);
    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);
    return res == CURLE_OK ? 0 : 1;
}
use std::env;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let api_key = env::var("API_KEY")?;
    let client = reqwest::Client::new();
    let body = serde_json::json!({
        "target": {
            "returns": [
                {"date": "2024-01-02", "value": 0.0012},
                {"date": "2024-01-03", "value": -0.0008}
            ]
        },
        "basket": {
            "AAPL": [
                {"date": "2024-01-02", "value": 0.0021},
                {"date": "2024-01-03", "value": -0.0015}
            ]
        },
        "config": {"max_allocation": 10, "rebalance_freq": "weekly"}
    });

    let response = client
        .post("https://replicurve.com/v1/replication")
        .header("Authorization", format!("Bearer {}", api_key))
        .json(&body)
        .send()
        .await?;

    println!("{}", response.text().await?);
    Ok(())
}
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

public class ReplicationExample {
    public static void main(String[] args) throws Exception {
        String apiKey = System.getenv("API_KEY");
        String body = """
            {
              "target": {
                "returns": [
                  {"date": "2024-01-02", "value": 0.0012},
                  {"date": "2024-01-03", "value": -0.0008}
                ]
              },
              "basket": {
                "AAPL": [
                  {"date": "2024-01-02", "value": 0.0021},
                  {"date": "2024-01-03", "value": -0.0015}
                ]
              },
              "config": {"max_allocation": 10, "rebalance_freq": "weekly"}
            }
            """;

        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create("https://replicurve.com/v1/replication"))
            .header("Authorization", "Bearer " + apiKey)
            .header("Content-Type", "application/json")
            .POST(HttpRequest.BodyPublishers.ofString(body))
            .build();

        HttpResponse<String> response = HttpClient.newHttpClient()
            .send(request, HttpResponse.BodyHandlers.ofString());

        System.out.println(response.body());
    }
}
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;

var apiKey = Environment.GetEnvironmentVariable("API_KEY");
using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization =
    new AuthenticationHeaderValue("Bearer", apiKey);

var body = """
{
  "target": {
    "returns": [
      {"date": "2024-01-02", "value": 0.0012},
      {"date": "2024-01-03", "value": -0.0008}
    ]
  },
  "basket": {
    "AAPL": [
      {"date": "2024-01-02", "value": 0.0021},
      {"date": "2024-01-03", "value": -0.0015}
    ]
  },
  "config": {"max_allocation": 10, "rebalance_freq": "weekly"}
}
""";

var response = await client.PostAsync(
    "https://replicurve.com/v1/replication",
    new StringContent(body, Encoding.UTF8, "application/json"));

Console.WriteLine(await response.Content.ReadAsStringAsync());

Starter (preset universe)

import os

import requests

response = requests.post(
    "https://replicurve.com/v1/replication",
    headers={"Authorization": f"Bearer {os.environ['API_KEY']}"},
    json={
        "target": {
            "returns": [
                {"date": "2024-01-02", "value": 0.0012},
                {"date": "2024-01-03", "value": -0.0008},
            ]
        },
        "basket": "sp500",
        "config": {"max_allocation": 10, "rebalance_freq": "weekly"},
    },
)
response.raise_for_status()
print(response.json())
const response = await fetch("https://replicurve.com/v1/replication", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    target: {
      returns: [
        { date: "2024-01-02", value: 0.0012 },
        { date: "2024-01-03", value: -0.0008 },
      ],
    },
    basket: "sp500",
    config: { max_allocation: 10, rebalance_freq: "weekly" },
  }),
});

if (!response.ok) throw new Error(await response.text());
console.log(await response.json());
#include <curl/curl.h>
#include <cstdlib>
#include <iostream>
#include <string>

int main() {
    const char* url = "https://replicurve.com/v1/replication";
    const char* api_key = std::getenv("API_KEY");
    const std::string body = R"({
  "target": {
    "returns": [
      {"date": "2024-01-02", "value": 0.0012},
      {"date": "2024-01-03", "value": -0.0008}
    ]
  },
  "basket": "sp500",
  "config": {"max_allocation": 10, "rebalance_freq": "weekly"}
})";

    CURL* curl = curl_easy_init();
    struct curl_slist* headers = nullptr;
    headers = curl_slist_append(
        headers, ("Authorization: Bearer " + std::string(api_key)).c_str());
    headers = curl_slist_append(headers, "Content-Type: application/json");

    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body.c_str());

    CURLcode res = curl_easy_perform(curl);
    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);
    return res == CURLE_OK ? 0 : 1;
}
use std::env;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let api_key = env::var("API_KEY")?;
    let client = reqwest::Client::new();
    let body = serde_json::json!({
        "target": {
            "returns": [
                {"date": "2024-01-02", "value": 0.0012},
                {"date": "2024-01-03", "value": -0.0008}
            ]
        },
        "basket": "sp500",
        "config": {"max_allocation": 10, "rebalance_freq": "weekly"}
    });

    let response = client
        .post("https://replicurve.com/v1/replication")
        .header("Authorization", format!("Bearer {}", api_key))
        .json(&body)
        .send()
        .await?;

    println!("{}", response.text().await?);
    Ok(())
}
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

public class ReplicationExample {
    public static void main(String[] args) throws Exception {
        String apiKey = System.getenv("API_KEY");
        String body = """
            {
              "target": {
                "returns": [
                  {"date": "2024-01-02", "value": 0.0012},
                  {"date": "2024-01-03", "value": -0.0008}
                ]
              },
              "basket": "sp500",
              "config": {"max_allocation": 10, "rebalance_freq": "weekly"}
            }
            """;

        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create("https://replicurve.com/v1/replication"))
            .header("Authorization", "Bearer " + apiKey)
            .header("Content-Type", "application/json")
            .POST(HttpRequest.BodyPublishers.ofString(body))
            .build();

        HttpResponse<String> response = HttpClient.newHttpClient()
            .send(request, HttpResponse.BodyHandlers.ofString());

        System.out.println(response.body());
    }
}
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;

var apiKey = Environment.GetEnvironmentVariable("API_KEY");
using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization =
    new AuthenticationHeaderValue("Bearer", apiKey);

var body = """
{
  "target": {
    "returns": [
      {"date": "2024-01-02", "value": 0.0012},
      {"date": "2024-01-03", "value": -0.0008}
    ]
  },
  "basket": "sp500",
  "config": {"max_allocation": 10, "rebalance_freq": "weekly"}
}
""";

var response = await client.PostAsync(
    "https://replicurve.com/v1/replication",
    new StringContent(body, Encoding.UTF8, "application/json"));

Console.WriteLine(await response.Content.ReadAsStringAsync());

Endpoints

GET /v1/health Health check
GET /v1/usage Monthly call count
POST /v1/replication Run replication
POST /v1/offset Run offset
POST /v1/validate Validate inputs

Request parameters

POST /v1/replication, POST /v1/offset, and POST /v1/validate share the same JSON body. Returns are daily simple returns (not log returns or prices). Each basket series must use exactly the same calendar dates as target.returns.

Top-level fields

Field Type Required Description
target object yes Return series to replicate or offset.
target.returns array yes At least two daily observations. Each element has date (ISO 8601, e.g. 2024-01-02) and value (simple daily return as a decimal, e.g. 0.0012 for +0.12%).
basket string or object yes Candidate instruments. A string selects a preset universe (Starter plans). An object maps ticker labels to return arrays (Professional plans and above). See basket formats below.
config object no Replication settings. Omitted fields use the defaults in config fields.

Config fields

All fields live under config and are optional.

Field Type Default Description
rebalance_freq "daily" | "weekly" "weekly" How often the engine recomputes portfolio weights. Weekly rebalances use a one-day implementation lag.
max_allocation integer ≥ 1 10 Maximum number of basket names held at any rebalance date. The engine prescreens candidates by trailing correlation before selecting up to this count.
direction "long" | "short" | "long_short" "long_short" Position sign constraint applied during weight estimation. long clips to non-negative weights, short to non-positive, and long_short allows both.
weight_normalization "none" | "gross" "none" Post-processing on each weight snapshot in the response. gross scales weights so absolute values sum to 1; none returns raw estimated weights.

Basket formats

Format Plans Description
Preset universe string Starter+ One of sp500, nasdaq100, ftse100, us_listed_etfs, or ucits_etfs. The service loads aligned daily returns for every name in that universe.
Custom return series Professional+ Object keyed by ticker label. Each value is a returns array with the same dates and length as target.returns. Labels must be non-empty strings; each series needs at least two points.

Endpoint behavior

Endpoint Effect
POST /v1/replication Estimates time-varying weights that track the target return series. Returns weights, aligned return series, and tracking metrics.
POST /v1/offset Same request body as replication. The engine negates the target returns before optimization, producing weights intended to neutralize target exposure.
POST /v1/validate Checks date alignment, missing values, and whether the overlapping history meets the rolling-window requirement (66 trading days by default). Does not run the full optimization.
GET /v1/usage Returns the authenticated key's monthly POST /v1/replication and POST /v1/offset count, plan allowance, and remaining calls for the current calendar month. Does not increment usage.

Response fields

Replication and offset (ReplicationResponse)

Field Type Description
weights array Sparse rebalance snapshots. Each element has date and weights (ticker → weight). Only dates where the portfolio changes are included; zero weights are omitted.
target array Aligned target return series used in the run (same shape as input, after alignment).
portfolio array Simulated portfolio return series produced by applying the estimated weights to the basket.
metrics object Sample period, tracking quality, return statistics, and allocation summary. See below.

metrics

Field Description
start, end, observations, period Aligned comparison window and return frequency.
correlation, r_squared, beta, alpha_annual Tracking quality versus the target series.
tracking_error, information_ratio, hit_rate, mean_error, slippage Active return and error statistics.
target_* and portfolio_* return fields Paired statistics (total_return, cagr, volatility, sharpe, max_drawdown) for target and portfolio.
avg_assets, turnover, gross_exposure Allocation summary over active rebalance dates.

Validate (ValidationResponse)

Field Description
valid true when inputs pass alignment and window checks; false otherwise.
overlapping_days Number of dates with non-missing target and basket data.
basket_size Count of basket series after resolution (preset universes expand to their full membership).
message Human-readable outcome. On failure, describes the validation error.

Health (HealthResponse)

Field Description
status Service health indicator (ok when the engine is reachable).
version Deployed engine package version.

Replication and offset methodology

High-level overview with live tracking statistics and charts from all ten site examples. Proprietary estimation details are intentionally omitted.

Download the whitepaper (PDF)

~10 pages — methodology, tracking metrics, and charts for every site example.

Download PDF

OpenAPI specification

Rendered from /assets/openapi.json, exported from the FastAPI app at deploy time.