Cap tables, option plans and dilution for Cyprus companies, as an event ledger.
Ledger
The append-only event ledger of a company: read it, append to it.
GET /companies/{company_id}/events
Read the event ledger
Every event recorded for the company, in ledger order, in exactly the shape POST accepts — the ledger is the source of truth and every projection is computed from it. Readers only (admin, viewer, auditor); an employee gets 403.
Parameters
Responses
Example 200 response
[
{
"type": "ShareClassDefined",
"date": "2025-01-15",
"class_id": "common",
"name": "Ordinary",
"kind": "common",
"authorized": 10000,
"nominal_value": "1.00"
},
{
"type": "Allotment",
"date": "2025-02-01",
"event_id": "A-2025-01",
"holder_id": "andreas",
"class_id": "common",
"quantity": 3500,
"price_per_share": "1.00"
}
]
POST /companies/{company_id}/events
Record an event
Append one event to the company's ledger. The event is checked against every capital rule — the same guards the portal's forms use — on the register as it stands with this event added; a refusal answers 409 with the rule's name and records nothing. Admins only. Event ids are yours to choose and unique per company, which makes a retry safe: the second POST of the same event answers 409 unique_event_key instead of recording it twice.
Parameters
Request body
one of the event types below — see Event types
Example: ShareClassDefined
{
"type": "ShareClassDefined",
"date": "2025-01-15",
"class_id": "common",
"name": "Ordinary",
"kind": "common",
"authorized": 10000,
"nominal_value": "1.00"
}
Example: HolderRecorded
{
"type": "HolderRecorded",
"date": "2025-01-15",
"event_id": "h-andreas-1",
"holder_id": "andreas",
"kind": "natural_person",
"legal_name": "Andreas Christofi",
"capacity": "own_account",
"address": "12 Makariou Ave, 1065 Nicosia, Cyprus"
}
Example: Allotment
{
"type": "Allotment",
"date": "2025-02-01",
"event_id": "A-2025-01",
"holder_id": "andreas",
"class_id": "common",
"quantity": 3500,
"price_per_share": "1.00"
}
Responses
Example 201 response
{
"event_key": "A-2025-01",
"event_type": "Allotment",
"seq": 42
}
Projections
What the ledger says on a date: register, vesting, pools.
GET /companies/{company_id}/register
Register of members on a date
The register projected from the events on or before as_of (default today): one line per holder with shares, options, stakes and the s.105 particulars, plus the instruments (ASAs, convertibles) that are not shares until they convert. Readers only.
Parameters
Responses
Example 200 response
{
"as_of": "2026-10-12",
"company_id": "co-lefkara",
"fully_diluted": 5000,
"instruments": [],
"instruments_total": "0",
"lines": [
{
"address": "12 Makariou Ave, 1065 Nicosia, Cyprus",
"consideration_recorded": "3500.00",
"date_entered": "2025-02-01",
"holder_id": "andreas",
"kind": "natural_person",
"legal_name": "Andreas Christofi",
"options": 0,
"outstanding": 3500,
"pct_fully_diluted": "70.0000",
"pct_outstanding": "77.7778"
}
],
"total_options": 500,
"total_outstanding": 4500
}
GET /companies/{company_id}/vesting
Option vesting on a date
Every option grant with what has vested, been exercised, cancelled or lapsed on as_of (default today). Readers only.
Parameters
Responses
Example 200 response
[
{
"cancelled": 0,
"exercisable": 120,
"exercised": 0,
"fmv_at_grant": "1.50",
"grant_id": "G-1",
"holder_id": "eleni",
"lapsed": 0,
"outstanding": 480,
"plan_id": "esop-2025",
"quantity": 480,
"valuation_at_grant": "val-2025",
"vested": 120
}
]
GET /companies/{company_id}/pools
Option pools on a date
Each option plan's pool on as_of (default today): size, granted, exercised, available. Readers only.
Parameters
Responses
Example 200 response
[
{
"available": 520,
"exercised": 0,
"granted": 480,
"plan_id": "esop-2025",
"pool_size": 1000
}
]
Modelling
Read-only calculations: a round, an IFRS 2 schedule.
POST /companies/{company_id}/rounds/model
Model a funding round
Model a round on the register at as_of (default today): price, pool top-up, instrument conversions, anti-dilution, the dilution table and the events that would make it real. Nothing is recorded, so readers may call it; recording the proposed events is a separate POST /events by an admin. 422 for terms that cannot be read, 409 with the guard name for a round the rules refuse.
Parameters
Request body
object
Example
{
"round_id": "seed",
"name": "Seed",
"kind": "seed",
"date": "2026-10-12",
"pre_money": "2000000",
"class_id": "common",
"subscriptions": [
{
"holder_id": "fund-x",
"amount": "500000"
}
]
}
Responses
Example 200 response
{
"adjustments": [],
"as_of": "2026-10-12",
"capitalization_post": "6250",
"company_id": "co-lefkara",
"conversions": [],
"dilution": [
{
"after": "3500",
"before": "3500",
"key": "andreas",
"kind": "holder",
"pct_after": "56.0000",
"pct_before": "70.0000"
}
],
"new_money": "500000",
"pool_pct_post": "8.0000",
"pool_top_up": 0,
"post": {
"capitalization": "6250",
"fully_diluted": 6250,
"instruments_total": "50000",
"total_options": 500,
"total_outstanding": 5750
},
"post_money_implied": "2500000",
"pre": {
"capitalization": "5000",
"fully_diluted": 5000,
"instruments_total": "50000",
"total_options": 500,
"total_outstanding": 4500
},
"price": "400.00",
"price_exact": "400",
"pricing_capitalization": "5000",
"proposed_events": [
{
"class_id": "common",
"date": "2026-10-12",
"event_id": "seed-fund-x",
"holder_id": "fund-x",
"price_per_share": "400.00",
"quantity": 1250,
"round_id": "seed",
"type": "Allotment"
}
],
"round_id": "seed",
"shares_issued": 1250,
"subscriptions": [
{
"amount": "500000",
"holder_id": "fund-x",
"paid": "500000.00",
"remainder": "0.00",
"shares": 1250
}
]
}
GET /companies/{company_id}/ifrs2
IFRS 2 expense schedule
The share-based payment expense for the period from–to (graded-vesting attribution, reversal on forfeiture). Grants without a recorded fair value are listed with their status and left out of the total — never given a guessed value. 409 guard_period for an inverted period. Readers only.
Parameters
Responses
Example 200 response
{
"company_id": "co-lefkara",
"complete": true,
"excluded_grants": 0,
"lines": [
{
"cumulative_end": "190.00",
"cumulative_start": "100.00",
"expense": "90.00",
"fair_value_per_option": "0.90",
"forfeited_unvested": 0,
"grant_id": "G-1",
"holder_id": "eleni",
"method": "black_scholes",
"plan_id": "esop-2025",
"quantity": 480,
"remaining": "242.00",
"reversed": "0.00",
"status": "ok",
"total_fair_value": "432.00"
}
],
"period_end": "2026-06-30",
"period_start": "2026-01-01",
"total_expense": "90.00"
}
Funds
Funds you are a member of and their own ledger.
GET /funds
List your funds
Every fund the token's owner is a member of, with their role in each.
Responses
Example 200 response
[
{
"id": "co-lefkara",
"name": "Lefkara Robotics Ltd",
"role": "admin"
}
]
POST /funds
Create a fund
Create a fund; the caller becomes its admin. 403 for an account that may not create one, 429 at the daily limit per user.
Request body
FundIn
Responses
Example 201 response
{
"id": "co-lefkara",
"name": "Lefkara Robotics Ltd",
"role": "admin"
}
GET /funds/{fund_id}/events
Read a fund's ledger
The fund's events the caller may read: the whole ledger for an admin, viewer or auditor; for a limited partner, its own rows and the fund-wide rows partners may see.
Parameters
Responses
Example 200 response
[
{
"type": "LpAdmitted",
"date": "2026-01-10",
"lp_id": "lp-a",
"legal_name": "Kyrenia Family Office Ltd",
"kind": "legal_entity",
"partner_role": "lp",
"commitment": "1000000"
}
]
POST /funds/{fund_id}/events
Record a fund event
Record one fund event. A capital call or distribution is written together with one allocation line per partner; 409 with the guard's name for a refusal. Admins only.
Parameters
Request body
one of the event types below — see Event types
Example: LpAdmitted
{
"type": "LpAdmitted",
"date": "2026-01-10",
"lp_id": "lp-a",
"legal_name": "Kyrenia Family Office Ltd",
"kind": "legal_entity",
"partner_role": "lp",
"commitment": "1000000"
}
Responses
Example 201 response
{
"events": [
{
"event_key": "call-3",
"event_type": "CapitalCallIssued",
"seq": 7
},
{
"event_key": "call-3|lp-a",
"event_type": "CallAllocated",
"seq": 8
}
]
}
POST /funds/{fund_id}/statements
Issue a partner statement
Issue a statement on a date with the figures the fund ledger gives: a header and one line per partner. Admins only.
Parameters
Request body
StatementIn
Responses
Example 201 response
{
"events": [
{
"event_key": "call-3",
"event_type": "CapitalCallIssued",
"seq": 7
},
{
"event_key": "call-3|lp-a",
"event_type": "CallAllocated",
"seq": 8
}
]
}
Models
CompanyOut
A company (or a fund) the caller is a member of, with the caller's role in it.
Example
{
"id": "co-lefkara",
"name": "Lefkara Robotics Ltd",
"role": "admin"
}
ConversionOut
One instrument converting in a modelled round.
Example
{
"amount": "50000",
"basis": "discount",
"cap_price": "800.00",
"discount_price": "320.00",
"holder_id": "angel-1",
"instrument_id": "asa-1",
"paid": "49920.00",
"price": "320.00",
"remainder": "80.00",
"shares": 156
}
DilutionLineOut
One line of the before/after dilution table.
Example
{
"after": "3500",
"before": "3500",
"key": "andreas",
"kind": "holder",
"pct_after": "56.0000",
"pct_before": "70.0000"
}
ErrorOut
The body of every error except a guard refusal: 401, 403, 404, 413, 429 and the 422s Ekvi raises itself. detail is one English sentence for a person; branch on the status code, not on the text.
Example
{
"detail": "role 'viewer' cannot record events"
}
EventRecorded
One event written to the ledger.
Example
{
"event_key": "A-2025-01",
"event_type": "Allotment",
"seq": 42
}
ExpenseLineOut
One grant in an IFRS 2 expense schedule. Amounts are null on a line that is not measured.
Example
{
"cumulative_end": "190.00",
"cumulative_start": "100.00",
"expense": "90.00",
"fair_value_per_option": "0.90",
"forfeited_unvested": 0,
"grant_id": "G-1",
"holder_id": "eleni",
"method": "black_scholes",
"plan_id": "esop-2025",
"quantity": 480,
"remaining": "242.00",
"reversed": "0.00",
"status": "ok",
"total_fair_value": "432.00"
}
ExpenseReportOut
IFRS 2 expense schedule for a period. complete is false when a grant could not be measured; such grants are listed with their status and left out of total_expense — never given a guessed value.
Example
{
"company_id": "co-lefkara",
"complete": true,
"excluded_grants": 0,
"lines": [
{
"cumulative_end": "190.00",
"cumulative_start": "100.00",
"expense": "90.00",
"fair_value_per_option": "0.90",
"forfeited_unvested": 0,
"grant_id": "G-1",
"holder_id": "eleni",
"method": "black_scholes",
"plan_id": "esop-2025",
"quantity": 480,
"remaining": "242.00",
"reversed": "0.00",
"status": "ok",
"total_fair_value": "432.00"
}
],
"period_end": "2026-06-30",
"period_start": "2026-01-01",
"total_expense": "90.00"
}
FundIn
A new fund; the caller becomes its admin.
Example
{
"name": "Akamas Ventures I"
}
FundRecordedOut
One recording in a fund: the event posted first, then the lines the road wrote with it.
Example
{
"events": [
{
"event_key": "call-3",
"event_type": "CapitalCallIssued",
"seq": 7
},
{
"event_key": "call-3|lp-a",
"event_type": "CallAllocated",
"seq": 8
}
]
}
GuardRefusalOut
409 Conflict: the request was understood and refused by a named capital rule. Nothing was recorded.
Example
{
"detail": {
"detail": "allotment of 2000 exceeds authorized 1000",
"guard": "guard_allotment_within_authorized"
}
}
HealthOut
Liveness of the installation. Needs no token.
Example
{
"deployment": "saas",
"edition": "saas",
"status": "ok"
}
HolderLineOut
One register line. The s.105 particulars are null when the holder has no HolderRecorded on the date; consideration_recorded is the subscription consideration recorded on the shares held, null for a holder that never held shares. The capacity (nominee, trustee) is not here: it is outside the register extract (Cap. 113 s.112).
Example
{
"address": "12 Makariou Ave, 1065 Nicosia, Cyprus",
"consideration_recorded": "3500.00",
"date_entered": "2025-02-01",
"holder_id": "andreas",
"kind": "natural_person",
"legal_name": "Andreas Christofi",
"options": 0,
"outstanding": 3500,
"pct_fully_diluted": "70.0000",
"pct_outstanding": "77.7778"
}
InstrumentOut
ASA / convertible instrument: not shares until conversion, hence separate from lines and outside FD.
Example
{
"amount": "50000",
"discount": "0.20",
"holder_id": "angel-1",
"instrument_id": "asa-1",
"longstop_date": "2027-06-30",
"valuation_cap": "4000000"
}
PoolLineOut
An option plan's pool on as_of.
Example
{
"available": 520,
"exercised": 0,
"granted": 480,
"plan_id": "esop-2025",
"pool_size": 1000
}
RatioAdjustmentOut
Anti-dilution adjustment in NVCA notation: CP2 = CP1 × (A + B) / (A + C); exact fractions are given as "num/den" strings.
Example
{
"a": "5000",
"b": "625",
"c": 1250,
"class_id": "pref-a",
"conversion_price_after": "360",
"conversion_price_before": "400",
"method": "broad_based_weighted_average",
"original_price": "400",
"ratio_after": "1.111111",
"ratio_before": "1"
}
RegisterOut
The register of members projected from the ledger on as_of.
Example
{
"as_of": "2026-10-12",
"company_id": "co-lefkara",
"fully_diluted": 5000,
"instruments": [],
"instruments_total": "0",
"lines": [
{
"address": "12 Makariou Ave, 1065 Nicosia, Cyprus",
"consideration_recorded": "3500.00",
"date_entered": "2025-02-01",
"holder_id": "andreas",
"kind": "natural_person",
"legal_name": "Andreas Christofi",
"options": 0,
"outstanding": 3500,
"pct_fully_diluted": "70.0000",
"pct_outstanding": "77.7778"
}
],
"total_options": 500,
"total_outstanding": 4500
}
RegisterSummaryOut
Totals of a register, before or after a modelled round.
Example
{
"capitalization": "5000",
"fully_diluted": 5000,
"instruments_total": "50000",
"total_options": 500,
"total_outstanding": 4500
}
Rejection
A capital rule that refused the request: the guard's stable name and its sentence.
Example
{
"detail": "allotment of 2000 exceeds authorized 1000",
"guard": "guard_allotment_within_authorized"
}
RoundModelOut
What the round would do — nothing is recorded. proposed_events are the ledger events a writer would post one by one to make it real; post is the register they would produce.
Example
{
"adjustments": [],
"as_of": "2026-10-12",
"capitalization_post": "6250",
"company_id": "co-lefkara",
"conversions": [],
"dilution": [
{
"after": "3500",
"before": "3500",
"key": "andreas",
"kind": "holder",
"pct_after": "56.0000",
"pct_before": "70.0000"
}
],
"new_money": "500000",
"pool_pct_post": "8.0000",
"pool_top_up": 0,
"post": {
"capitalization": "6250",
"fully_diluted": 6250,
"instruments_total": "50000",
"total_options": 500,
"total_outstanding": 5750
},
"post_money_implied": "2500000",
"pre": {
"capitalization": "5000",
"fully_diluted": 5000,
"instruments_total": "50000",
"total_options": 500,
"total_outstanding": 4500
},
"price": "400.00",
"price_exact": "400",
"pricing_capitalization": "5000",
"proposed_events": [
{
"class_id": "common",
"date": "2026-10-12",
"event_id": "seed-fund-x",
"holder_id": "fund-x",
"price_per_share": "400.00",
"quantity": 1250,
"round_id": "seed",
"type": "Allotment"
}
],
"round_id": "seed",
"shares_issued": 1250,
"subscriptions": [
{
"amount": "500000",
"holder_id": "fund-x",
"paid": "500000.00",
"remainder": "0.00",
"shares": 1250
}
]
}
StatementIn
Issue a partner statement on a date with the figures the fund ledger gives.
Example
{
"date": "2026-06-30",
"statement_id": "st-2026-q2"
}
SubscriptionOut
One new-money subscription in a modelled round.
Example
{
"amount": "500000",
"holder_id": "fund-x",
"paid": "500000.00",
"remainder": "0.00",
"shares": 1250
}
ValidationErrorOut
422 for a query or path parameter FastAPI could not read (for example as_of=yesterday). An event body Ekvi cannot read answers 422 with an ErrorOut instead.
Example
{
"detail": [
{
"loc": [
"query",
"as_of"
],
"msg": "Input should be a valid date",
"type": "date_parsing"
}
]
}
VestingLineOut
One option grant and where its vesting stands on as_of.
Example
{
"cancelled": 0,
"exercisable": 120,
"exercised": 0,
"fmv_at_grant": "1.50",
"grant_id": "G-1",
"holder_id": "eleni",
"lapsed": 0,
"outstanding": 480,
"plan_id": "esop-2025",
"quantity": 480,
"valuation_at_grant": "val-2025",
"vested": 120
}
Event types
The body of POST /companies/{company_id}/events (Event.*) and of POST /funds/{fund_id}/events (FundEvent.*): an object with type set to the event's name and the fields below. Unknown fields are refused; the company or fund is never a field — it is the one in the path.
Event.Allotment
Issue (allotment) of shares to a holder. price_per_share is the subscription price, not the nominal value.
Event.AsaSubscribed
Subscription to an advance subscription agreement (the Cyprus SAFE) — an instrument, not shares. Before conversion the holder is not a member: it does not enter the Register of Members, issued or FD (Cap. 113 s.105; position 2 of the 2026-09-18 report). event_id is the instrument's identifier, and InstrumentConverted refers to it.
Event.AuthorizedCapitalIncreased
Increase of a class's authorized capital by a general meeting resolution (Cap. 113 s.62). new_authorized is the class's whole new ceiling, not an increment: this way the event reads without arithmetic over history. resolution_date is when the resolution was passed; HE14 (15 days) is counted from it, while date is when the projection starts seeing the new ceiling (usually the same date).
Event.Cancellation
Cancellation of shares a holder holds in a class: the holding and the issued count go down by quantity. More than the holder holds is refused; reason says why the shares were cancelled.
Event.ConversionRatioAdjusted
Anti-dilution: the class's new conversion_ratio after a down round (Ekvi derives it per NVCA: CP2 = CP1 × (A + B) / (A + C) for weighted average, CP2 = round price for full ratchet; ratio = OIP / CP2). new_ratio is the whole new value, not an increment. Adjustments go up only: a ratio below the current one is refused (guard_ratio_not_decreased); cause_round_id must be a recorded round (guard_round_defined).
Event.FundingRoundRecorded
A round as a record: an informational event that Allotment.round_id and InstrumentConverted.round_id refer to (a reference to an unrecorded round is refused by guard_round_defined). It issues no shares and changes no holding: price and amounts are deal terms; the issue is separate Allotment events. round_id is the identifier (like class_id of a class); date is when the projection starts seeing the round (usually close_date).
Event.GrantFairValueRecorded
IFRS 2: fair value of one option of a grant at grant date (equity-settled: measured once and not remeasured, IFRS 2.10–11), so a second event for the same grant is refused (guard_fair_value_once). method — the model (Black-Scholes, binomial, …), source — who valued it.
Event.HolderRecorded
The particulars of a holder — the member as an entity behind the opaque holder_id.
Event.InstrumentConverted
Conversion of an instrument into shares: behaves like an Allotment to the instrument's holder (authorized, nominal value, basis = quantity × price_per_share) and extinguishes the instrument. One instrument converts once. round_id — the round in which the instrument became shares; as for Allotment, a reference to an unrecorded round is refused (guard_round_defined).
Event.OpeningBalanceRecorded
A holder's position as it stood on a date, entered without the events that produced it — the first line of a register whose history before that date is not in this ledger (an import of a snapshot: "Andreas holds 3,500,000 ordinary shares today").
Event.OptionGranted
A grant of options with vesting terms. vesting_start defaults to the grant date when not given.
Event.OptionPlanAdopted
Option plan: the pool and the share class issued on exercise. Grants above the pool are refused.
Event.OptionsCancelled
Cancellation (forfeiture) of a grant's unexercised options (leaver, expiry). Above the remainder is refused.
Event.OptionsExercised
Exercise: the grant's holder receives shares of the plan's class. Above vested is refused.
Event.OwnershipDeclared
Who owns a legal-entity holder, as the company was told: one link of the ownership chain the register of beneficial owners is built from (rule cy.ubo.legal_entity_member_requires_look_through).
Event.ShareClassDefined
A share class and its terms on exit.
Event.Transfer
Transfer of shares between holders. date is the date the transfer is recorded in the Register of Members (the projection changes from it, and HE57 is counted from it too); the execution date of the instrument itself is not modelled (5B).
Event.ValuationRecorded
FMV per share on a date. A grant with no valuation on or before its date is recorded and carries the gap (Register.valuation_on, VestingLine.fmv_at_grant is None); it is not refused.
FundEvent.CapitalCallIssued
A call header. Its lines are CallAllocated events, one per partner with a non-zero line, weighted by uncalled commitment just before the call.
FundEvent.ContributionReceived
Money a partner paid against its line of a call; several partial payments are allowed.
FundEvent.DistributionDeclared
A distribution header, paid on its date. Its lines are DistributionAllocated events: return of capital weighted by unreturned contributed capital, gain weighted by called capital.
FundEvent.ExpensePaid
A fee or a fund expense paid out of the fund's cash. The fee is entered, not computed (fee bases differ from one LPA to the next); call_id names the call that funded it, when one did.
FundEvent.FundConstituted
The fund exists from this date. Every other event must be on or after it, and there is one.
FundEvent.InvestmentMade
A tranche into a position. The first tranche opens the position; a later one with the same position_id is a follow-on into the same portfolio company. The cost is the transaction price, which is the fair value at initial recognition (IFRS 13): the position is carried at cost until a mark.
FundEvent.InvestmentMarked
The fair value of the whole remaining position on this date. A portfolio round price is an input to a mark, never the mark itself; disclosure_ref cites the company's disclosure it rests on.
FundEvent.InvestmentRealized
An exit. cost_realized is the part of the remaining cost that leaves the position; when it is all of it, the exit is full and the position closes, with nothing left to value. remaining_fair_value is the value of what stays, and acts as a mark on the exit's date.
FundEvent.LpAdmitted
A partner joins with a commitment. Only before the first call: a later closing changes every allocation weight retroactively (equalisation), which v1 does not model.
FundEvent.StatementWithdrawn
A statement is withdrawn, never edited; a replacement is a new statement with its own id. While a statement stands, an event dated on or before it that changes its figures is refused. A withdrawn statement stands on no date, not even before the withdrawal's; the store refuses a withdrawal dated after the day it is recorded.