Logo We do IT right

ONE Platform


ONE Platform: Structured Notes: Integration API

Basics

To operate with our platform, we offer REST JSON API, including the following abilities:

  1. Create new security
  2. Update existing security
  3. Request for securities and generated documents
  4. 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:

  1. Every security has its’ own unique name so you can refer to security by name easily
  2. 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.
  3. 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.
  4. 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.
  5. Blotter version is reserved and should be set to 1.
  6. 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.
  7. 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:

1SecurityAlredyExistException
2SecurityMissingException
3IncorrectSecurityDataException
5SecurityUpdatingException
20UnderlyingAlreadyExistException
21IncorrectUnderlyingDataException
22UnderlyingMissingException
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: