ONE Platform: Structured Notes: Integration API
Basics
To operate with our platform, we offer REST JSON API, including the following abilities:
- Create new security
- Update existing security
- Request for securities and generated documents
- Authentication
It is the basic API to push the securities from other platforms and systems to ours; and once we receive a new security request we’ll start workflow on it (including generation of the documents, editing, approval and then filling).
The basic concepts are:
- Every security has its’ own unique name so you can refer to security by name easily
- In addition, security has a unique UUID-based identifier named BlotterId; it might be self-generated for every security or might refer to an external one, as you wish; but keep in mind it should be unique.
- If you have decided to generate a fresh uuid as BlotterId, we have an ExternalBlotterId for you to make references between ours and your data.
- In case you already have CUSIP or ISIN, please specify it in the request. It will impact our workflow (we’ll not ask for CUSIP from the user) and in addition you'll be able to find data by CUSIP and/or ISIN as well. We know for FWP these values might not be available so you can omit it easily if you haven't.
- Blotter version is reserved and should be set to 1.
- The available file formats to download are PDF or DOCX for now; you cannot download other artifacts like EDGAR forms or expense fees for now. Please send us a request for extension if you need these types as well.
- If you want to create security, you do not need to specify huge texts and disclosures. Instead, you should specify the master template name with all texts filled in. To get the possible master template names you should use GetMasterSecurityTemplatesList as the possible names to use.
For the authentication we’re using standard JWT tokens and KeyCloak.
For test purposes, we are able to make the endpoints freely accessible without JWT tokens, and the security context will be disabled. But for the production we’ll definitely switch security on back.
For sample JSONs, please refer to JSONs folder.
The proper server URL is looks like https://sandbox.nadlab.one/app/api/ImportDataApi/CreateOrUpdate.
Retrieve securities and generated content
[get] /app/api/ImportDataApi/GetSecurityPublishedData ( String format, // pdf | docx (String blotterId, // either blotter Id or security name should be passed String securityName, String cusip, // reserved int version, // reserved, should be 1 String documentType) // FWP | 424b2 -> ImportSecuritiesExceptionResponse | file body
So, if you know the security name, or BlotterId, you can request a PDF or MS Word (if it exists) file to download.
[get] /app/api/ImportDataApi/GetSecurityData ( String blotterId, String securityName, String cusip, String documentType) -> ImportSecuritiesExceptionResponse | SecurityData
If you want to get the SecurityData form from the existing security (you should know either the name or BlotterId or CUSIP) it is the endpoint for you.
[get] /app/api/ImportDataApi/GetSecuritiesList () -> ImportSecuritiesExceptionResponse | BaseSecurityData[]
This is the API call to get all known securities from the system. It returns a base array with names and IDs for further requests.
[get] /app/api/ImportDataApi/GetMasterSecurityTemplatesList () -> ImportSecuritiesExceptionResponse | BaseSecurityData[]
This is the API call to get all known security templates from the system. It returns a base array with names and IDs for further requests.
Create new security
[post] /app/api/ImportDataApi/NewSecurity (SecurityData) -> ImportSecuritiesExceptionResponse | ImportSecuritiesResponse
Creates new security and enforces workflow on it to be started.
[post] /app/api/ImportDataApi/CreateOrUpdate (SecurityData) -> ImportSecuritiesExceptionResponse | ImportSecuritiesResponse
For the new security, simply call NewSecurity; but if security already exists, call UpdateSecurity instead.
[post] /app/api/ImportDataApi/NewSecurityMultipleCUSIPs ( MultipleCusipSecurityData) -> ImportSecuritiesExceptionResponse | ImportSecuritiesResponse
This is a specific form to create securities with multiple CUSIPs to be assigned on it. Very specific case for specific securities. Has a more detailed structure with extra fields as an extension to the SecurityData.
Mark security as unused (deletion)
[delete] /app/api/ImportDataApi/DeleteSecurity (SecurityData) -> ImportSecuritiesExceptionResponse | ImportSecuritiesResponse
Marks the security to be canceled. All existing workflows are terminated as well. Needs an Name / BlotterId to be passed to find the security from the list.
Update existing security
[post] /app/api/ImportDataApi/UpdateSecurity (SecurityData) -> ImportSecuritiesExceptionResponse | ImportSecuritiesResponse
Updates the existing security. If the update is not limited to ISIN / CUSIP assignment, stops the current workflow, generates the new document, and then starts a new workflow on top of it (e. G. very similar to pair Delete/New).
[post] /app/api/ImportDataApi/UpdateSecurityMultipleCUSIPs ( MultipleCusipSecurityData) -> ImportSecuritiesExceptionResponse | ImportSecuritiesResponse
Updates to the existing security were created by NewSecurityMultipleCUSIPs with the same notes as for the UpdateSecurity. This method is necessary in case you need an update of the multiple-CUSIPs security and therefore you cannot use the usual UpdateSecurity method as it has no necessary fields to be used.
Group operations
[post] /app/api/ImportDataApi/BatchCreateNewSecurities (BatchSecurityData) -> ImportSecuritiesExceptionResponse | ImportSecuritiesResponse[]
Allows to make several securities at once, rather than foreach with the CreateOrUpdate call.
[post] /app/api/ImportDataApi/BatchRequestUpdateSecurity (BatchSecurityData) -> ImportSecuritiesExceptionResponse | ImportSecuritiesResponse[]
Allows to update several securities at once, rather than foreach with the CreateOrUpdate call.
[delete] /app/api/ImportDataApi/BatchDeleteSecurities (BatchSecurityData) -> ImportSecuritiesExceptionResponse | ImportSecuritiesResponse[]
Removes a couple of the securities in the same manner as forDeleteSecurity in cycle.
Common data structures
Actually, we have two kinds of classes here: input and output DTOs. The input data is huge and contains hundreds of fields; the response is simple and all time it either exception or data were requested.
Class SecurityData
This is a huge data structure so we’re splitted it by regions, as follows:
Required fields
[required unique] public String Name; // should be unique for every security
[required] String BlotterID; // UUID
int BlotterIdVersion; // 1, non-null
[required] String Type; // FWP or 424b2
String ProductCode;
[required] boolean IsSampleTS; // true for test security
String ExternalBlotterID; // ID from external system
// When creating a security, it copies data from the master document.
// The master document is searched for by the name, CUSIP, ISIN or
// BlotterId, so one of these fields are required
[required] String CopyDataFromSecurity;
String CopyDataFromSecurityByCUSIP;
String CopyDataFromSecurityByBlotterID;
Common fields
String ISIN;
String DocumentType;
String SettlementType; //Physical, Cash
[required] String Issuer;
[required] BigDecimal TaxProb10;
[required] BigDecimal TaxProb15;
[required] BigDecimal TaxProb20;
String SprcId;
String AveragingFrequency;
String AveragingWindow;
String UnderlierType; // Single Asset, Worst Of, Share weighted
UnderlyingWithCusip[] Underlyings;
SpotReference[] SpotReferences;
BigDecimal UpsideParticipation;
BigDecimal PrincipalAmount;
String Term;
BigDecimal HypotheticalInitialValue;
LocalDate PricingDate;
LocalDate TradeDate;
LocalDate OriginalIssueDate;
LocalDate FinalValuationDate;
LocalDate MaturityDate;
BigDecimal UnderwritingDiscount;
BigDecimal PlatformFee;
String PlatformFeeType; //Physical, Cash
BigDecimal MaxUnderwritingDiscountPlatformFee;
String ProductType;
BigDecimal OfferingAmount;
BigDecimal EstimatedInitialValueStart;
BigDecimal EstimatedInitialValueEnd;
BigDecimal EstimatedInitialValue424b2;
BigDecimal EstimatedInitialValue;
EstimatedInitialValueRange EstimatedInitialValueRange;
Payoff details
String ProtectionType; // Barrier, Geared, Hard, Geared, Unprotected
BigDecimal ProtectionLevel;
BigDecimal ProtectionLevelStrike;
String ProtectionBarrierObservationType; //GearedPut, Daily, Continuous, European
BigDecimal ProtectionBarrierLevel;
String ProtectionBarrierTouchBreak; //Break, Touch
BigDecimal DownsideParticipation;
BigDecimal DownsideCap;
BigDecimal DownsideCapStrike;
BigDecimal UpsideParticipationStrike;
BigDecimal UpsideCap;
BigDecimal UpsideCapStrike;
BigDecimal UpsideJumpLevel;
BigDecimal UpsideJumpStrike;
String UpsideJumpTouchBreak; //Break, Touch
BigDecimal AbsoluteReturnBarrierLevel;
String AbsoluteReturnBarrierTouchBreak; //Break, Touch
BigDecimal AbsoluteReturnParticipation;
BigDecimal AbsoluteReturnParticipationStrike;
BigDecimal AbsoluteReturnJumpLevel;
BigDecimal AbsoluteReturnJumpStrike;
String AbsoluteReturnJumpTouchBreak; //Break, Touch
BigDecimal AbsoluteReturnCap;
BigDecimal AbsoluteReturnCapStrike;
CouponDetails
String CouponUnderlierReturnType; //Simple Return
String CouponPaymentType; //Periodic
BigDecimal AccrualBarrier;
String AccrualBarrierTouchBreak; //Break, Touch
BigDecimal Coupon;
BigDecimal AnnualizedCoupon;
BigDecimal CouponStep;
String CouponStepFrequency;
BigDecimal CouponParticipation;
BigDecimal CouponParticipationStrike;
BigDecimal CouponBarrier;
BigDecimal CouponBarrierStep;
String CouponBarrierStepFrequency;
String CouponBarrierTouchBreak;
String CouponBarrierObservationType; //GearedPut, Daily, Continuous, European
BigDecimal MinCoupon;
String Memory; //Yes, No
Coupon details - Autocallables
String CouponFrequency; //Monthly, Quarterly, Annual, Semiannual
String ObservationCalendar;
CallableDetails
String CallableType;
BigDecimal NonCallPeriods;
BigDecimal AutocallLevel;
BigDecimal AutocallLevelStep;
String AutocallLevelStepFrequency;
String AutocallTouchBreak; //Break, Touch
BigDecimal CallPremium;
BigDecimal CallPremiumStep;
String CallPremiumStepFrequency; //Monthly, Quarterly, Annual, Semiannual
Coupon Details - Autocallables
String CallFrequency; //Monthly, Quarterly, Annual, Semiannual
String RollConvention; //Observation, Payment
String IsCallable; //Yes, No
Boolean HasCallPremium;
Boolean IsCustomSchedule;
String CallDeterminationDelay;
Cover
LocalDate EquityIndexUnderlyingSupplementFilingDate;
LocalDate EtfUnderlyingSupplementFilingDate;
LocalDate FilingDate;
LocalDate ProspectusFilingDate;
LocalDate ProspectusSupplementFilingDate;
String RegistrationNumber;
BigDecimal TotalProceedsToIssuer;
BigDecimal TotalUnderwritingDiscount;
BigDecimal TotalPriceToPublic;
BigDecimal ProceedsToIssuer;
String DocumentTitle;
Term
String SettlementDelay;
String BookEntryForm;
String FinalLevel;
String InitialLevel;
String Listing;
String PaymentAtMaturity;
String ReferenceAssetType; //Equity Index
String ReferenceReturnSummary;
LocalDateTime LookbackInitialDate;
LocalDateTime LookbackFinalDate;
RangedParam RangedParam;
BigDecimal DepositRate;
BigDecimal PutRate;
String FinalCustomer;
AveragingSchedule[] AveragingSchedule;
Term - Autocallables
CouponSchedule[] CouponSchedule;
CallSchedule[] CallSchedule;
Class BatchSecurityData
SecurityData[] BatchRequest;
Class AveragingSchedule
LocalDate averagingDate;
Class CallSchedule
LocalDate CallDeterminationDate;
LocalDate CallDate;
String Callable; //Yes, No
BigDecimal AutocallLevel;
String AutocallTouchBreak; //Break, Touch
BigDecimal CallPremium;
Class CouponSchedule
[required] String CouponBarrierObservationType; //GearedPut, Daily,
// Continuous, European
BigDecimal Coupon;
LocalDate CouponPaymentDate;
BigDecimal CouponBarrier;
String CouponBarrierTouchBreak; //Break, Touch
LocalDate CouponDeterminationDate;
Boolean Memory;
Class EstimatedInitialValueRange
BigDecimal Min;
BigDecimal Max;
Class MultipleCusipSecurityData
[required] SecurityData[] Products;
Class RangedParam
[required] String Name; //UpsideCap, UpsideJumpLevel, UpsideParticipation,
// ProtectionLevel, ProtectionBarrierLevel,
// Coupon, CallPremium, MinCoupon
BigDecimal Min;
BigDecimal Max;
Class SpotReference
[required] String Ticker;
[required] BigDecimal Spot;
Class UnderlyingWithCusip
BigDecimal Weight;
[required] String Ticker;
UnderlyingWithCusip[] Basket;
String Name;
BigDecimal EstimatedInitialValue;
BigDecimal MaxCap;
RangedParam RangedParam;
String ISIN;
BigDecimal UnderwritingDiscount;
EstimatedInitialValueRange EstimatedInitialValueRange;
BigDecimal PlatformFee;
BigDecimal MaxUnderwritingDiscountPlatformFee;
BigDecimal TotalPriceToPublic;
BigDecimal TotalUnderwritingDiscount;
BigDecimal TotalProceedsToIssuer;
BigDecimal ProceedsToIssuer;
BigDecimal Coupon;
BigDecimal ProtectionLevelStrike;
GregorianCalendar ObservationDate;
GregorianCalendar CouponPaymentDate;
BigDecimal DepositRate;
BigDecimal PutRate;
BigDecimal TaxProb10;
BigDecimal TaxProb15;
BigDecimal TaxProb20;
Class ImportSecuritiesExceptionResponse
Integer errorCode;
String errorMessage;
The typical error codes are:
| 1 | SecurityAlredyExistException |
| 2 | SecurityMissingException |
| 3 | IncorrectSecurityDataException |
| 5 | SecurityUpdatingException |
| 20 | UnderlyingAlreadyExistException |
| 21 | IncorrectUnderlyingDataException |
| 22 | UnderlyingMissingException |
Cass ImportSecuritiesResponse
Integer security_id ;
String message;
Class BaseSecurityData
String Name;
String CUSIP;
String BlotterID;
Integer BlotterIdVersion;
String Type; // FWP | 424b2
Authentication
We support JWT tokens and oAuth2, so you need to ask KeyCloak server for the token:
curl -L -X POST 'https://jpm.nadlab.one/keycloak/realms/bm4a/protocol/openid-connect/token' \ -H 'Content-Type: application/x-www-form-urlencoded' \ --data-urlencode 'client_id=**app_id**' \ --data-urlencode 'grant_type=password' \ --data-urlencode 'client_secret=**secret here** \ --data-urlencode 'scope=openid' \ --data-urlencode 'username=**login** \ --data-urlencode 'password=**password**'
As result, you’ll get the standard answer like this:
{
"access_token":"eyJ...PmQ",
"expires_in": 600,
"refresh_expires_in": 1800,
"refresh_token":"eyJ...mM",
"token_type": "bearer",
"id_token":"eyJ...7ug",
"not-before-policy": 0,
"session_state": "22c...64f",
"scope": "openid email profile"
}
So all you need is to keep the access token and then add it to every request as a bearer, e. g. add the following header for every request:
Authorization: Bearer **token**
Then you’re free to login again if it is necessary or request for the token refresh instead, both ways works.
Error handling
Basically, we follow the best practices to return status 200 if everything went OK, and JSON with error explanation and code 40x for other cases. Of course, the other standard errors like 500 are also possible as well, due to standard web architecture design limitations.
Just as illustration, the API internals might be demonstrated (in simplified form of course) as the following snippet:
public ResponseEntity GetSecuritiesList() {
ResponseEntity responseEntity = null;
try {
List<BaseSecurityData> securitesList = _securityService.getSecuritiesList();
responseEntity = new ResponseEntity<Object>(securitesList, HttpStatus.OK);
} catch (Exception ex) {
responseEntity = getExceptionResponseEntity(ex, HttpStatus.BAD_REQUEST);
}
return responseEntity;
}
At practice, it means:
- If any transport error occurs, you’ll get HTTP code 500 without any JSONs but text.
- If any application error occurs, you’ll get code 40x and JSON attached with explanation in text form as a message field.
- If any logic error occurs (like wrong params or so) you’ll get code 200 and error explained in JSON, as above. In addition, you’ll see reasonable error codes in the JSON field named errorCode.
- If no errors occur, you’ll get the response in sole JSON, without any ancient tricks like paging or so. Just the whole result at once. Fortunately, we have only one API call with a possibly long list as a response, and there is GetSecuritiesList alone.
Few useful links:
- The “Getting Started” for API.
- Swagger for integration API.
