API reference

Everything you need to call replication, offset, and hedge endpoints — OpenAPI spec, code samples, and parameter reference. Regenerated on every deployment.

Getting started

All endpoints live under /v1. Send JSON with Content-Type: application/json and authenticate with a Bearer token. Request a key to get started.

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/hedge-sector Offset sector ETF proxy
POST /v1/hedge-factor Offset factor ETF proxy
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/hedge-sector Accepts a canonical sector name. The server resolves it to a US-listed sector ETF, loads ETF and S&P 500 returns, and runs offset mode internally. Returns the same ReplicationResponse as /v1/offset.
POST /v1/hedge-factor Accepts a canonical factor name. The server resolves it to a US-listed factor ETF, loads ETF and S&P 500 returns, and runs offset mode internally. Returns the same ReplicationResponse as /v1/offset.
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 metered POST count (/v1/replication, /v1/offset, /v1/hedge-sector, /v1/hedge-factor), plan allowance, and remaining calls for the current calendar month. Does not increment usage.

Hedge requests

POST /v1/hedge-sector and POST /v1/hedge-factor use a different request schema — no target or basket in the body. The server resolves the name to a US-listed ETF proxy, loads aligned returns, and runs offset mode against a fixed S&P 500 basket. Invalid sector or factor names return 422.

Request fields

Field Type Required Description
sector enum yes on /v1/hedge-sector One of eleven GICS sector names (see allowlist below). Invalid values fail validation with 422.
factor enum yes on /v1/hedge-factor One of five factor names (see allowlist below). Invalid values fail validation with 422.
start date no Sample start date. Default 2019-06-01.
end date no Sample end date. Omitted means latest available data.
config object no Same ReplicationConfig fields as replication and offset (rebalance_freq, max_allocation, etc.).
exclude_tickers string[] no Tickers excluded from the S&P 500 basket.

Example request

POST /v1/hedge-sector
{
  "sector": "technology",
  "config": { "rebalance_freq": "weekly", "max_allocation": 10 }
}

Sector allowlist

Sector name ETF proxy
technologyXLK
financialsXLF
energyXLE
healthcareXLV
consumer_discretionaryXLY
consumer_staplesXLP
industrialsXLI
materialsXLB
utilitiesXLU
real_estateXLRE
communicationXLC

Factor allowlist

Factor name ETF proxy
momentumMTUM
qualityQUAL
valueVLUE
low_volatilityUSMV
sizeIWM

Response fields

Replication, offset, and hedge (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.