Sandbox Walkthrough
One investor from account creation to a binding subscription, as a sequence of real calls you can run against the sandbox.
Integrating through Apex AscendOS?The product guidance on this page applies to you. The endpoints, fields and statuses are Monark's; use the AscendOS equivalents listed in Building on Apex AscendOS.
This walkthrough takes a single investor from nothing to a binding subscription in an SPV. Run it against the sandbox with your sandbox key before you build any UI — it is the shortest path to understanding what the integration actually requires.
Every request carries your key:
Authorization: <your sandbox API key>
Content-Type: application/jsonBase URL: https://sandbox-api.monark-markets.com
1. Find Your Financial Institution
Every investor, offering and market-data call is scoped by a financial institution.
GET /primary/v1/financial-institutionTake the id of the one marked isDefault, or the one matching the entity you are distributing through. Call it {fiId} below.
2. Create the Investor's Party
An investing party is a person or an entity. For an individual account, create a person:
POST /primary/v2/person{
"firstName": "Sarah",
"lastName": "Chen",
"email": "[email protected]",
"dateOfBirth": "1985-04-12",
"isUSBased": true,
"citizenship": "US",
"taxId": "000000000",
"phoneCountryCode": "1",
"phoneNumber": "5555550123",
"physicalAddress": {
"street1": "100 Market Street",
"city": "San Francisco",
"state": "CA",
"zipCode": "94105",
"countryCode": "US"
},
"financialInstitutionId": "{fiId}",
"referenceId": "your-own-party-reference"
}Expect 201. Keep the returned id — it is the partyId, and it doubles as the investor id on the v1 investor endpoints.
Note: For an entity account,
POST /primary/v2/entityinstead, then add at least one controlling individual withPOST /primary/v2/entity/{id}/individuals. An entity with no active individuals is refused at order time.
3. Open the Account
POST /primary/v2/account{
"financialInstitutionId": "{fiId}",
"registrationType": "Individual",
"externalAccountNumber": "IND-0001",
"owners": [
{ "partyId": "{personId}", "type": "Person", "isPrimary": true }
]
}Expect 201. Keep the returned id as {accountId}.
externalAccountNumber is your own account number. It comes back on reads and is the easiest way to reconcile Monark's records with your ledger.
4. Record Accreditation
PUT /primary/v2/account/{accountId}/accreditation{ "accreditationStatus": "NET_WORTH_GT_1M" }Expect 204. Accrediting the account is what activates its owners for private offerings. Without this, every subscription is refused with Accreditation not set.
Accreditation expires after 12 months. See Accreditation for all 15 statuses and what each one asserts.
5. Find an Investable Offering
GET /primary/v1/pre-ipo-company-spv/investor/{partyId}?monarkStage=PRIMARY_FUNDRAISE&pageSize=25Then keep only the SPVs where isApproved is true. Both conditions are required: the stage means Monark has opened it for fundraising, and the flag means your platform has approved it in the Alts Desk. An SPV missing either is not distributable to your investors.
Take one SPV's id as {spvId} and note three fields from it:
| Field | Why you need it |
|---|---|
minCommitmentAmount | The floor for a subscription |
remainingDollarAllocation | The ceiling |
allInPricePerShare | The price the subscription fills at, used for the share estimate |
6. Read the Offering in Full
GET /primary/v1/pre-ipo-company-spv/{spvId}/investor/{partyId}?includeDocuments=trueThis returns the deal terms and the document set — the PPM, risk factors, series agreement and subscription agreement preview. In production these must be available to the investor before they commit. See SPV View.
7. Create the Transaction
Before this call, in production, you would check the investor's cash balance and lock the amount. Monark does not hold investor cash and cannot see it.
POST /primary/v2/transaction{
"targetAssetType": "PreIPOCompanySPV",
"targetId": "{spvId}",
"side": "Subscription",
"investorId": "{partyId}",
"amountReservedDollars": 25000,
"referenceId": "your-own-order-reference"
}Expect 201. The response is the transaction; keep its id as {transactionId}. Its status will be Pending.
Try the refusals here too — they are the ones your UI has to explain:
- An amount below
minCommitmentAmount→ "Investment amount is lower than the minimum commitment amount." - An amount above
remainingDollarAllocation→ "Investment amount is greater than the remaining dollar allocation."
Note: Send an
IdempotencyKeyheader on this call. Retrying with the same key after a timeout returns the cached response rather than creating a second subscription. Keys are held for 24 hours.
8. Read the Required Actions
GET /primary/v2/transaction-action/transaction/{transactionId}This returns a bare array, not a paginated envelope. Each action carries a type, a status of Pending or Complete, a responsibleParty, and — for document actions — a dataId naming the document.
Keep the actions whose responsibleParty is Investor or Partner. Monark, Advisor and CoOwner actions are not yours to complete.
The five types are TextAcknowledge, ViewDisclosure, DocumentAcknowledge, DocumentSign and Questionnaire. See Transaction Actions for how to present each.
9. Fetch the Documents
GET /primary/v2/document?transactionId={transactionId}&pageSize=100Build a map of document id to url. An action's dataId is the document id, which is how you know which document a DocumentAcknowledge or DocumentSign action refers to.
10. Complete Each Action
PUT /primary/v2/transaction-action/{actionId}/completeSend an empty body. Expect 204. This endpoint accepts every action type.
Two exceptions to "just complete it":
A DocumentSign action needs the document route to record the signature. Completing it here marks the action done, but does not stamp signedAt or the signing metadata on the document. For anything you need an audit trail on — the subscription agreement above all — sign it properly:
POST /primary/v2/document/{dataId}/signImportant: Signing a document auto-completes its associated action, but it does not trigger the promotion check. So sign your documents first, then call
completeon a remaining action — or callcomplete-all— so the transaction actually advances. A flow that signs every document and then stops will leave the transaction sitting inPendingwith every action marked complete.
A Questionnaire action needs its answers first. Submit them to /primary/v1/questionnaire-answer, then complete the action. Otherwise it is refused with "no answers were found for Questionnaire".
The bulk alternative, PUT /primary/v2/transaction-action/transaction/{transactionId}/complete-all, completes everything it can and skips what it cannot, rather than failing the batch. Re-read the actions afterwards rather than assuming it cleared them all.
11. Watch It Become Binding
Re-read the transaction:
GET /primary/v2/transaction/{transactionId}Once the last action for a responsible party is complete, the transaction moves from Pending to Processing, and the subscription agreement carries the investor's signature.
At Processing the commitment is binding and can only be reversed manually. In production you would also have received a SubscriptionStatus webhook at each transition — note that the webhook reports the v1 status vocabulary. See Webhooks.
From here the SPV fills, moves to PRE_CLOSE_REVIEW, Monark requests the wire, and on close the transaction becomes Complete and the investor holds a position.
What To Try Next
| Scenario | Why |
|---|---|
| An entity account | The controlling-individual requirement catches most integrations out |
| An expired accreditation | Set an accreditation, then confirm the 12-month rule by testing an investor whose status predates it |
| An indication of interest | The non-binding path against a prospective offering. See Indications of Interest |
| A second subscription to the same SPV | Confirms how your UI handles an investor with an existing position |
| Webhook delivery | Register an endpoint and watch SubscriptionStatus arrive at each transition |
Next Steps
- Confirm you are ready for production: Go-Live Checklist
- The refusals in full: Errors & Reason Codes
Updated about 17 hours ago