openapi: "3.0.0" info: version: 0.1.0 description: >- REST endpoints for interacting with WealthCounsel data, such as contacts and matters. ## Registration ### For 3rd Party Partners * Send an email to [api.partnerships@wealthcounsel.com](mailto:api.partnerships@wealthcounsel.com) requesting that your application be added as an approved 3rd party partner. You will need to provide the following information: * Application name * Application description * Company name * Primary contact email * Redirect URI * Website URL * Logo image url * Requested access (available scopes: contacts read, contacts write, matters read, matters write) * After approval, we will create a test practice that your company can use to test the integration. An email will be sent to the primary contact email with credentials to our site. * Login to the member website with the credentials specified in the email from the step above and navigate to My Practice > Practice Admin > Integrations. You will see your app listed under 3rd Party Integrations. By clicking the **Add Application** button, you will be redirected back to your application to start the authorization process. * Also on the Integrations page, you can click **View Details** on your app to get your client id and client secret that will be needed to obtain authorization for any of our API endpoints. * Until your app is ready to be enabled, it will be restricted to only be an option on the test practice we created for you. When your app is ready to go live, please notify us using the API email address above and we will make a configuration change that will allow any practice to see your application as an integration option. ## OAuth 2.0 ### 3rd-Party API Access (OAuth 2.0 Authorization Code Grant Type) #### Overview The authorization grant flow is a little more complicated, but follows this simple flow: - Your application opens a browser to send the user to the OAuth server (https://member.wealthcounsel.com/oauth) - The user sees the authorization prompt and approves the app’s request - The user is redirected back to the application (to the registered redirect URL) with an authorization code in the query string - The application exchanges the authorization code for an access token (by sending an HTTP request to https://api.wealthcounsel.com/oauth2/token) You can find a more detailed explanation here: [What is the OAuth 2.0 Authorization Code Grant Type?](https://developer.okta.com/blog/2018/04/10/oauth-authorization-code-grant-type) #### Example Flow First, your application would redirect a user to a location similar to this: ``` https://member.wealthcounsel.com/oauth ?response_type=code &client_id=_YOUR_CLIENT_ID_ &redirect_uri=_A_URI_IN_YOUR_APPLICATION_THAT_WILL_EXCHANGE_AUTH_CODE_FOR_ACCESS_TOKEN_ &scope=contact:read+contact:write (something like this) &state=_SOME_RANDOM_STRING_YOU_CAN_USE_TO_VERIFY_ ``` When the user logs in at member.wealthcounsel.com it will redirect them - via the redirect_uri - to an endpoint in your application, like: ``` https://example-app.com/auth/redirect ?code=g0ZGZmNjVmOWIjNTk2NTk4ZTYyZGI3 &state=_SAME_RANDOM_STRING_YOU_SENT_TO_AUTH_SERVER_ ``` This endpoint can then take that authorization code and exchange it for a token that can be used to make calls against the API. This allows authorization without requiring sending credentials over and over again to the API. To do the exchange your application sends an HTTP POST to https://api.wealthcounsel.com/oauth2/token: ``` Host: https://api.wealthcounsel.com Authorization: Basic wNTQ6MTkwZDRmMTBmOTYxZThiNzAyZjlkMTVkY2Nl Content-Type: application/x-www-form-urlencoded Accept: application/json grant_type=authorization_code&redirect_uri=https://lawmetrics.com/auth/redirect&code=g0ZGZmNjVmOWIjNTk2NTk4ZTYyZGI3 ``` And that will give a token, which can then be used to make any API calls, and a refresh token to use when your access token expires, as well as when the access token will expire: ``` HTTP/1.1 200 OK Content-Type: application/json Cache-Control: no-store Pragma: no-cache { "access_token":"MTQ0NjJkZmQ5OTM2NDE1ZTZjNGZmZjI3", "token_type":"bearer", "expires_in":3600, "refresh_token":"IwOGYzYTlmM2YxOTQ5MGE3YmNmMDFkNTVk", "scope":"create delete" } ``` The refresh token expires in 30 days, but you get a new one every refresh, so only if a user doesn’t access your application for a month will they have to re-login to member.wealthcounsel.com. The token can then be used in the Authorization header of all API requests: ``` GET /v1/contacts Host: https://api.wealthcounsel.com Authorization: Bearer MTQ0NjJkZmQ5OTM2NDE1ZTZjNGZmZjI3 ``` ### Refreshing Tokens (OAuth 2.0 Refresh Token Grant Type) For security reasons the access token your application has an expiration and will need to be occasionally refreshed. This is done by using the refresh token to request a new access token: ``` POST /oauth2/token HTTP/1.1 Host: https://api.wealthcounsel.com Authorization: Basic wNTQ6MTkwZDRmMTBmOTYxZThiNzAyZjlkMTVkY2Nl Content-Type: application/x-www-form-urlencoded Accept: application/json grant_type=refresh_token&refresh_token=fdb8fdbecf1d03ce5e6125c067733c0d51de209c ``` When you get a new access token you will also be issued another refresh token. NOTE: If the refresh token is expired your application will have to have the user login again, or in the case of a member registration use the username/password to obtain new access and refresh tokens. title: WealthCounsel API servers: - url: https://api.wealthcounsel.com/v1 description: API tags: - name: Authorization description: This API uses OAuth 2.0 for authorizing requests against the endpoints. These requests are not made against the main API (https://api.wealthcounsel.com)/v1, and are instead made against different paths/servers. Please pay special attention to which domain each authentication endpoint is located (`https://api.wealthcounsel.com` vs. `https://member.wealthcounsel.com`). More details are in the documentation of the endpoints below. - name: Matters description: Endpoints that allow querying and modification of matters information. - name: Contacts description: Endpoints for querying and modifying contact information paths: /oauth: get: tags: - Authorization summary: >- (https://member.wealthcounsel.com) Requests authorization for the specified client. description: | Used by 3rd party integrations for getting an authorization code that can be used to exchange for a token as part of the "authorization code" OAuth 2.0 flow. **NOTE:** This page is accessed at `https://member.wealthcounsel.com/oauth`, not `https://api.wealthcounsel.com/oauth` because this is the page the users will be navigating to in order to grant access. ### Registration In order to use this endpoint you must first be registered and given your client id, client secret, and redirect uri (see above). These can be used by the 3rd party tool to get the user's authorization and obtain a code, which it will then use to obtain an access token. ### Obtaining the Code In order to obtain the code the user needs to authorize the specified client for use. This is usually done by providing a link to the authorization server at `/oauth` (instead of directly to `/oauth/authorize`) that contains the parameters described below. When the user follows the link they will be prompted to log in and be given information on your client and be able to approve/deny the use. They will then be redirected to the URI used to register your client (which should match the one provided in the link) which will have an authorization code you can use to obtain an access token and refresh token from the `/token` endpoint. ### NOTE: Member integrations don't need to use this because they have a username/password that can be used directly with the `/token` endpoint with the "password" grant type. servers: - url: https://member.wealthcounsel.com description: Authorization host parameters: - name: response_type in: query description: >- Type of authorization operation required: true schema: type: string enum: - code - name: client_id in: query required: true description: The client identifer you received when your application was registered. schema: type: string - name: redirect_uri in: query required: true description: >- The URI to return the user to after authorization is complete - should match the one given at registration schema: type: string - name: scope in: query description: >- One or more scope values (space-separated) indicating which parts of the user's account you wish to access schema: type: string - name: state in: query description: >- An opaque value used by the client to maintain state between the request and callback. The authorization server includes this value when redirecting the user-agent back to the client. The parameter SHOULD be used for preventing cross-site request forgery schema: type: string responses: 302: description: >- A redirect to the specified client's redirect uri as configured when the application is registered, containing the generated authorization code that can be used to request an access token. headers: location: schema: type: string example: https://example-app.com/cb?code=AUTH_CODE_HERE&state=1234zyx /oauth2/token: post: tags: - Authorization summary: (https://api.wealthcounsel.com) Allows requesting/granting of access tokens for interaction with the API. description: >- You can use this endpoint to obtain a secure token to use for authorization to the REST services. Depending on the `grant_type` the required parameters and the schema of the response are slightly different. Currently supported is the "authorization code" and "password" OAuth 2.0 flows. For examples of how to form the different types of requests see the description above. ## Grant Type Descriptions ### Grant type "authorization_code" Used by a 3rd party integration when requesting a token for the first time you would use "authorization_code", and to get a new token when you have a refresh token you would use "refresh_token". The required fields are "code" and "redirect_uri" ### Grant type "password" Used by a member's custom integration when requesting a token for the first time you would use "password", and to get a new token when you have a refresh token you would use "refresh_token". The required fields are "username" and "password". ### Grant type "refresh_token" Used to get a new token. The only required field is "refresh_token" servers: - url: https://api.wealthcounsel.com parameters: - name: Authorization in: header description: >- The base64-encoded string with the client id and client secret, in the form of `client_id:client_secret`, passed in the header in the form `Basic encoded-string`. This is required for getting a token and for getting a refresh token at a later time. With a `client_id` of `abcdef` and a `client_secret` of `ff5wega2` you would have a header value of `Basic YWJjZGVmOmZmNXdlZ2Ey` schema: type: string format: byte requestBody: required: true content: application/x-www-form-urlencoded: schema: type: object properties: grant_type: description: >- Type of grant method being used to obtain an access token. Depending on the grant type specified you need to supply different information. type: string enum: - authorization_code - password - refresh_token code: type: string description: >- **Required** for grant type "authorization_code". The authorization code obtained from the authorization server when the user authorized the client to interact with this service on their behalf. Required for grant_type "authorization_code" redirect_uri: type: string description: >- **Required** for grant type "authorization_code". The URI to return the user to after authorization is complete - should match the one given at registration. This is used to help validate the client and is required when. Required for grant_type "authorization_code" username: type: string description: >- **Required** for grant type "password". Member's custom integration username password: type: string description: >- **Required** for grant type "password". Member's custom integration password refresh_token: type: string description: >- **Required** for grant type "refresh_token". The refresh token given when the original token request was given. The refresh token is required when requesting a new token after the originally issued one has expired. A new access token and new refresh token will be granted if the refresh token is still valid. required: - grant_type responses: 200: description: Ok content: application/json: schema: $ref: '#/components/schemas/TokenResponse' /contacts: post: tags: - Contacts summary: Add a new contact requestBody: description: >- An contact record, with references to matters. The full matter information is not required in order to properly save the contact, just the id references to them. required: true content: application/json: schema: $ref: '#/components/schemas/ContactInput' responses: 201: $ref: '#/components/responses/Created' 300: $ref: '#/components/responses/Invalid' 401: $ref: '#/components/responses/Unauthorized' security: - Custom Integrations OAuth 2: - contacts:write - 3rd-Party Integrations OAuth 2: - contacts:write get: tags: - Contacts summary: Returns a list of contacts parameters: - $ref: '#/components/parameters/offset' - $ref: '#/components/parameters/limit' - name: sort in: query required: false schema: type: string enum: - name - modified - name: externalId in: query description: >- The value used to associate the contact with a 3rd Party system. required: false schema: type: string - name: name in: query description: >- The full or partial name of a contact to search for. At least 3 characters are required. required: false schema: type: string responses: 200: description: Ok content: application/json: schema: type: array items: $ref: '#/components/schemas/Contact' security: - Custom Integrations OAuth 2: - contacts:read - 3rd-Party Integrations OAuth 2: - contacts:read /contacts/{id}: get: tags: - Contacts summary: Get a contact with the specified ID parameters: - $ref: '#/components/parameters/id' responses: 200: description: Ok content: application/json: schema: $ref: '#/components/schemas/Contact' security: - Custom Integrations OAuth 2: - contacts:read - 3rd-Party Integrations OAuth 2: - contacts:read put: tags: - Contacts summary: >- Update a contact with the specified ID. For convenience A full Contact object can be submitted from this endpoint (like those returned from the GET endpoints), but all read-only fields will retain their current values. parameters: - $ref: '#/components/parameters/id' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/ContactInput' responses: 204: $ref: '#/components/responses/Updated' 300: $ref: '#/components/responses/Invalid' 401: $ref: '#/components/responses/Unauthorized' security: - Custom Integrations OAuth 2: - contacts:write - 3rd-Party Integrations OAuth 2: - contacts:write delete: tags: - Contacts summary: Delete a contact with the specified ID parameters: - $ref: '#/components/parameters/id' responses: 204: description: Deleted security: - Custom Integrations OAuth 2: - contacts:write - 3rd-Party Integrations OAuth 2: - contacts:write /matters: post: tags: - Matters summary: Add a new matter requestBody: description: >- A matter record, with references to clients and contacts. The full contact information is not required in order to properly save the matter, just the id references to them. required: true content: application/json: schema: $ref: '#/components/schemas/NewMatterInput' responses: 201: $ref: '#/components/responses/Created' 300: $ref: '#/components/responses/Invalid' 401: $ref: '#/components/responses/Unauthorized' security: - Custom Integrations OAuth 2: - matters:write - 3rd-Party Integrations OAuth 2: - matters:write get: tags: - Matters summary: Returns a list of matters parameters: - $ref: '#/components/parameters/offset' - $ref: '#/components/parameters/limit' responses: 200: description: Ok content: application/json: schema: type: array items: $ref: '#/components/schemas/Matter' security: - Custom Integrations OAuth 2: - matters:read - 3rd-Party Integrations OAuth 2: - matters:read /matters/{id}: get: tags: - Matters summary: Get a matter with the specified ID parameters: - $ref: '#/components/parameters/id' responses: 200: description: Ok content: application/json: schema: $ref: '#/components/schemas/Matter' security: - Custom Integrations OAuth 2: - matters:read - 3rd-Party Integrations OAuth 2: - matters:read put: tags: - Matters summary: >- Update a matter with the specified ID. For convenience a full Matter object can be submitted from this endpoint (like those returned from the GET endpoints), but all read-only fields will retain their current values. parameters: - $ref: '#/components/parameters/id' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/MatterInput' responses: 204: $ref: '#/components/responses/Updated' 300: $ref: '#/components/responses/Invalid' 401: $ref: '#/components/responses/Unauthorized' security: - Custom Integrations OAuth 2: - matters:write - 3rd-Party Integrations OAuth 2: - matters:write delete: tags: - Matters summary: Delete a matter with the specified ID parameters: - $ref: '#/components/parameters/id' responses: 204: description: Deleted security: - Custom Integrations OAuth 2: - matters:write - 3rd-Party Integrations OAuth 2: - matters:write components: parameters: id: name: id in: path description: >- The identifier for an entity required: true schema: type: string offset: name: offset in: query description: >- The starting index within the record set required: false schema: type: integer default: 0 limit: name: limit in: query required: false description: >- The number of records to return in a record set, up to a maximum of 1000 records per request schema: type: integer default: 20 maximum: 1000 responses: Created: description: Created headers: location: description: The URI of the newly created resource schema: type: string Updated: description: Updated NotFound: description: The specified resource was not found content: application/json: schema: $ref: '#/components/schemas/Error' Invalid: description: The submitted resource was invalid content: application/json: schema: $ref: '#/components/schemas/Error' Unauthorized: description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error' schemas: Contact: allOf: - $ref: '#/components/schemas/ContactInput' - type: object properties: organizationName: type: string description: The name of this individual's company. This field is read-only, but used only for contacts of type "individual". created: type: string description: Read-only. The date the contact was created. format: date modified: type: string description: Read-only. The date the contact was last modified. format: date matters: type: array description: Read-only. An array of matters in which this contact is referenced or matters for WealthTracx™ administrations in which this contact is referenced. items: type: object properties: id: type: string name: type: string ContactInput: allOf: - $ref: '#/components/schemas/NewContactInput' - type: object required: - id properties: id: type: string description: Read-only. The system identifier for the contact. NewContactInput: type: object required: - type properties: type: type: string enum: - individual - entity - trust - charity client: type: boolean description: >- Indicates whether a contact is a client. resident: type: boolean description: >- Indicates whether a is a client resident. usCitizen: type: string description: >- Indicates whether a is a client US Citizen. enum: - Unknown - United States Citizen - Not United States Citizen prefix: type: string description: This field is optional, but used only for contacts of type "individual" enum: - Mr. - Mrs. - Ms. - Miss - Dr. - Mx. name: type: string description: >- Concatenated field formed using first, middle, and last name values. This field is read-only for contacts of type "individual". firstName: type: string description: This field is required for contacts of type "individual" middleName: type: string description: This field is optional, but used only for contacts of type "individual" lastName: type: string description: This field is required for contacts of type "individual" suffix: type: string description: This field is optional, but used only for contacts of type "individual" enum: - Jr. - Sr. - II - III - IV - V - other suffixName: type: string description: This field is optional, but used only for contacts of type "individual" aka: type: string description: >- Also Known As; nickname or other name used. This field is optional, but used only for contacts of type "individual" preferredName: type: string description: >- Client's preferred name. This field is optional, but used only for contacts of type "individual" organizationId: type: string description: The ID of this individual's company. This field is optional, but used only for contacts of type "individual". title: type: string description: This field is optional, but used only for contacts of type "individual". gender: type: string description: This field is required for contacts of type "individual" enum: - male - female # ssn: # type: string # description: This field is optional, but used only for contacts of type "individual" maritalStatus: type: string description: This field is optional, but used only for contacts of type "individual" enum: - Divorced - Life Partner - Married - Single - Widowed marriageDate: type: string description: The "maritalStatus" value must be "Married" and the value must be in the format of YYYY-MM-DD format: date pattern: '^\d{4}-\d{2}-\d{2}$' status: type: string description: This field is optional, but used only for contacts of type "individual" enum: - Living - Deceased - Incapacitated spouseId: type: string description: This field is optional, but used only for contacts of type "individual" spouseName: type: string description: >- Deceased spouse's name can be entered here when marital status is "Widowed". This field is optional, but used only for contacts of type "individual" spouseDeathDate: type: string format: date description: This field is optional, but used only for contacts of type "individual" addresses: type: array items: type: object properties: type: type: string enum: - work - home street: type: string city: type: string state: type: string zip: type: string county: type: string countyRef: type: string description: County reference enum: - Bourough - City - City and County - County - Parish - Other country: type: string emails: type: array items: type: object properties: type: type: string enum: - work - home address: type: string format: email phones: type: array items: type: object properties: type: type: string enum: - work - home - mobile - fax number: type: string sites: type: array description: >- Website(s) associated with contact. items: type: object properties: url: type: string ims: type: array description: >- Instant Messenger ID(s) associated with contact items: type: object properties: user: type: string notes: type: string entityType: type: string description: This field is optional and is only used for contacts of type "entity" or "charity" enum: - Corporation - Partnership - LLC - Other entityName: type: string description: >- Holds value for entity type when "Other" is specified. This field is optional and is only used for contacts of type "entity" or "charity". entityContactId: type: string description: The ID of this contact person. Who this represents depends on the entity's type (Officer Contact, General Partner, Managing Member). This field is optional, but used only for contacts of type "entity" or "charity". taxation: type: string description: >- This field is optional and is only used for contacts of type "entity" or "charity" enum: - Partnership - C-Corporation - S-Corporation - Disregarded Entity - Non-Taxable # taxpayerID: # type: string # description: >- # Taxpayer ID number should be in the format XX-XXXXXXX. This field is optional and is only used for # contacts of type "entity" or "charity" formationState: type: string description: >- State in which the entity was formed. This field is optional and is only used for contacts of type "entity" or "charity". minLength: 2 maxLength: 2 dob: type: string format: date description: This field is optional, but used only for contacts of type "individual" with status of "Deceased" # ein: # type: string # description: Estate EIN. Only for contacts of type "individual" with status of "Deceased" employers: type: array description: List of employers. Only for contacts of type "individual" with status of "Deceased" items: type: object properties: id: type: string status: type: string enum: - Retired - Employed placeOfDeath: type: object description: Place of Death. Only for contacts of type "individual" with status of "Deceased" properties: city: type: string state: type: string county: type: string countyRef: type: string description: County reference enum: - Bourough - City - City and County - County - Parish - Other country: type: string externalId: type: string description: A value used to associate the contact with a 3rd Party system Matter: allOf: - $ref: '#/components/schemas/MatterInput' - type: object properties: attorney: type: string description: The name of the attorney. created: type: string description: Read-only. The date when the matter was created. format: date modified: type: string description: Read-only. The date when the matter was last modified format: date NewMatterInput: type: object required: - clients - name - status - type properties: name: type: string type: type: string enum: - estate-planning - business-planning - administration-death - medicaid-planning - veterans-benefits-planning desc: type: string refnum: type: string description: >- An optional field to include any specific naming convention or case reference system the user wishes to associate with the matter. administrationDesignation: type: string description: The Administration Designation, only applicable if type is "administration-death" enum: - trust-administration - estate-administration - trust-and-estate-administration openDate: type: string format: date description: The date this matter was opened. closeDate: type: string format: date description: The date this matter was closed. clients: type: array items: type: object required: - id properties: id: type: string attorneyid: type: string description: The contact ID of the attorney. notes: type: string description: Notes for this matter. status: type: string enum: - Active - Inactive MatterInput: allOf: - $ref: '#/components/schemas/NewMatterInput' - type: object required: - id properties: id: type: string description: Read-only. The system identifier of a matter. TokenResponse: type: object properties: access_token: type: string description: The access token to put in a request header when interacting with this API. token_type: type: string description: The type of access token, currently only "Bearer" for use in an Authorization header. enum: - bearer expires_in: type: string description: >- The number of seconds the access token will be valid. If the token expires you will need to use the refresh token to obtain a new access token. format: int32 refresh_token: type: string description: >- A longer-lasting token that can be used to request a new access token when it has expired. Can be used without user interaction as long as it is not expired. If the refresh token has expired as well you will need to obtain a new authorization code from the authorization server, or in the case of the "password" grant flow use the username/password to get a new set of tokens. Error: required: - code - message properties: code: type: string message: type: string Authorization Code: type: object required: - grant_type - code - redirect_uri properties: grant_type: description: >- Type of grant method being used to obtain an access token. Used by a 3rd party integration when requesting a token for the first time you would use "authorization_code", and to get a new token when you have a refresh token you would use "refresh_token". type: string enum: - authorization_code code: type: string description: >- The authorization code obtained from the authorization server when the user authorized the client to interact with this service on their behalf. Required for grant_type "authorization_code" redirect_uri: type: string description: >- The URI to return the user to after authorization is complete - should match the one given at registration. This is used to help validate the client and is required when. Required for grant_type "authorization_code" Refresh Token: type: object required: - grant_type - refresh_token properties: grant_type: description: >- Type of grant method being used to obtain an access token. Used to get a new token. type: string enum: - refresh_token refresh_token: type: string description: >- The refresh token given when the original token request was given. The refresh token is required when requesting a new token after the originally issued one has expired. A new access token and new refresh token will be granted if the refresh token is still valid. Password: type: object required: - grant_type - username - password properties: grant_type: description: >- Type of grant method being used to obtain an access token. Used by a member's custom integration when requesting a token for the first time you would use "password", and to get a new token when you have a refresh token you would use "refresh_token". type: string enum: - password username: type: string description: >- Member's custom integration username password: type: string description: >- Member's custom integration password securitySchemes: Custom Integrations OAuth 2: type: oauth2 flows: password: tokenUrl: https://api.wealthcounsel.com/oauth2/token scopes: contacts:read: Read contact information contacts:write: Modify contact information matters:read: Read matters information matters:write: Modify matters information 3rd-Party Integrations OAuth 2: type: oauth2 flows: authorizationCode: authorizationUrl: https://member.wealthcounsel.com/oauth tokenUrl: https://api.wealthcounsel.com/oauth2/token refreshUrl: https://api.wealthcounsel.com/oauth2/token scopes: contacts:read: Read contact information contacts:write: Modify contact information matters:read: Read matters information matters:write: Modify matters information