anchor.diff:                      | (false, _) -> failwith error
                       | (true, responseCode) ->
                           match responseCode with
  -                            | 201 -> JsonConvert.DeserializeObject<HockeyResponse>(response.Messages.[0])
  +                            | 201 -> JsonConvert.DeserializeObject<'TResponse>(response.Messages.[0])
                               | _ -> failwith error
  -            | _ -> failwith error+            | _ -> failwith error
  +
  +
  +/// Uploads an app to HockeyApp
  +/// ## Parameters
  +///  - `setParams` - Function used to override the default parameters
  +///
  +/// ## Sample
  +///
  +///     HockeyApp (fun defaults ->
  +///         {defaults with
  +///             AppId = ...
  +///             ApiToken = ...
  +///             ...
  +///         })
  +
  +let HockeyApp setParams = processHockeyAppCmd<HockeyAppUploadParams, HockeyResponse> HockeyAppUploadDefaults setParams validateParams toCurlArgs
  +
  +/// Create a new version of an app on HockeyApp
  +/// ## Parameters
  +///  - `setParams` - Function used to override the default parameters
  +///
  +/// ## Sample
  +///
  +///     HockeyAppVersions (fun defaults ->
  +///         {defaults with
  +///             AppId = ...
  +///             ApiToken = ...
  +///             Version = ...
  +///             ...
  +///         })
  +
  +let HockeyAppVersion setParams = processHockeyAppCmd<HockeyAppVersionParams, HockeyVersionResponse> HockeyAppVersionDefaults setParams validateVersionParams toVersionCurlArgs
  
positive.code:     param
  
  /// [omit]
  let private toCurlArgs param = seq {
      yield (String.Format("-sL -w \"{0}%{{http_code}}{0}\"", Regex.Escape(nl)))
      yield sprintf "-H \"X-HockeyAppToken:%s\"" param.ApiToken
      yield sprintf "-F \"ipa=@%s\"" param.File
      if not (String.IsNullOrEmpty param.Dsym) then yield sprintf "-F \"dsym=@%s\"" param.Dsym
  
negative.code:     if not (String.IsNullOrEmpty param.CommitSHA) then yield sprintf "-F \"commit_sha=%s\"" param.CommitSHA
      if not (String.IsNullOrEmpty param.BuildServerUrl) then yield sprintf "-F \"build_server_url=%s\"" param.BuildServerUrl
      if not (String.IsNullOrEmpty param.RepositoryUrl) then yield sprintf "-F \"repository_url=%s\"" param.RepositoryUrl
  
      if not (String.IsNullOrEmpty param.AppId) && (String.IsNullOrEmpty param.VersionId) then 
          yield sprintf "https://rink.hockeyapp.net/api/2/apps/%s/app_versions/upload" param.AppId
      else if not (String.IsNullOrEmpty param.AppId) && not (String.IsNullOrEmpty param.VersionId) then
          yield sprintf "https://rink.hockeyapp.net/api/2/apps/%s/app_versions/%s" param.AppId param.VersionId
      else
          yield "https://rink.hockeyapp.net/api/2/apps/upload"
  }
  
  /// [omit]
  let private toVersionCurlArgs (param:HockeyAppVersionParams) = seq {
      yield (String.Format("-sL -w \"{0}%{{http_code}}{0}\"", Regex.Escape(nl)))
      yield "-X POST"
      yield "-H \"Content-Type: application/json\""
      yield sprintf "-d '{\"bundle_version\":\"%s\"}'" param.Version
      yield sprintf "-H \"X-HockeyAppToken:%s\"" param.ApiToken
      yield sprintf "https://rink.hockeyapp.net/api/2/apps/%s/app_versions/new" param.AppId
  }
  
  /// [omit]
  let private processHockeyAppCmd<'TParam, 'TResponse when 'TParam :> BaseHockeyAppParams> defaults
      (setParams: 'TParam -> 'TParam) 
      (validateParam: 'TParam -> 'TParam) 
      (toCurlArgs: 'TParam -> seq<string>) =
      let p = defaults
              |> setParams
              |> validateParam
  
      p
      |> toCurlArgs
  