Post Command - HTTP API Command Line Utils

Post Command is a collection of command line utils that lets you easily discover, inspect and invoke ServiceStack endpoints from a single command.

All command line utils are available in the latest dotnet tool which can be installed from:

dotnet tool install --global x

Or if you had a previous version installed, update with:

dotnet tool update -g x

inspect command

The inspect command lets you inspect features and APIs available on a remote ServiceStack endpoint including the version of ServiceStack running, the App's registered Content Types, Plugins and Auth Providers as well as its public APIs, their routes and Response Types.

Use x inspect to display usage examples and available options:

Usage: x inspect <base-url>
       x inspect <base-url> <request>
       x inspect <base-url> <request> -lang <csharp|python|typescript|dart|java|kotlin|swift|fsharp|vbnet>
       x inspect <base-url> <request> -lang <cs|py|ts|da|ja|kt|sw|fs|vb>

inspect ServiceStack App

This this command to display high-level information about the endpoint in a human-friendly format, e.g:

Output:

Base URL:           https://techstacks.io
Name:               TechStacks!
Version:            5.111

Content Types:      application/json, application/xml, application/jsv, text/html, text/jsonreport, text/csv
Plugins:            html, csv, autoroutes, metadata, ssref, httpcache, svg, sharp, auth, sitemap, cors, validation, autoquerymeta, autoquery, openapi, session
Auth Providers:     twitter (oauth), github (oauth), jwt (Bearer), servicestack (credentials)

| #   | Api                             | Routes                                         | Response                                |
|-----|---------------------------------|------------------------------------------------|-----------------------------------------|
| 1   | Ping                            | /ping                                          |                                         |
| 2   | GetOrganization                 | GET:/orgs/{Id}                                 | GetOrganizationResponse                 |
| 3   | GetOrganizationBySlug           | GET:/organizations/{Slug}                      | GetOrganizationResponse                 |
| 4   | GetOrganizationMembers          | GET:/orgs/{Id}/members                         | GetOrganizationMembersResponse          |
| 5   | GetOrganizationAdmin            | GET:/orgs/{Id}/admin                           | GetOrganizationAdminResponse            |
| 6   | CreateOrganizationForTechnology | POST:/orgs/posts/new                           | CreateOrganizationForTechnologyResponse |
| 7   | CreateOrganization              | POST:/orgs                                     | CreateOrganizationResponse              |
| 8   | UpdateOrganization              | PUT:/orgs/{Id}                                 | UpdateOrganizationResponse              |
| 9   | DeleteOrganization              | DELETE:/orgs/{Id}                              |                                         |
| 10  | LockOrganization                | PUT:/orgs/{Id}/lock                            |                                         |
| 11  | AddOrganizationLabel            | POST:/orgs/{OrganizationId}/labels             | OrganizationLabelResponse               |
| 12  | UpdateOrganizationLabel         | PUT:/orgs/{OrganizationId}/members/{Slug}      | OrganizationLabelResponse               |
| 13  | RemoveOrganizationLabel         | DELETE:/orgs/{OrganizationId}/labels/{Slug}    |                                         |
| 14  | AddOrganizationCategory         | POST:/orgs/{OrganizationId}/categories         | AddOrganizationCategoryResponse         |
| 15  | UpdateOrganizationCategory      | PUT:/orgs/{OrganizationId}/categories/{Id}     | UpdateOrganizationCategoryResponse      |
| 16  | DeleteOrganizationCategory      | DELETE:/orgs/{OrganizationId}/categories/{Id}  |                                         |
| 17  | AddOrganizationMember           | POST:/orgs/{OrganizationId}/members            | AddOrganizationMemberResponse           |
| 18  | UpdateOrganizationMember        | PUT:/orgs/{OrganizationId}/members/{Id}        | UpdateOrganizationMemberResponse        |
| 19  | RemoveOrganizationMember        | DELETE:/orgs/{OrganizationId}/members/{UserId} |                                         |
| 20  | SetOrganizationMembers          | POST:/orgs/{OrganizationId}/members/set        | SetOrganizationMembersResponse          |
| 21  | GetOrganizationMemberInvites    | GET:/orgs/{OrganizationId}/invites             | GetOrganizationMemberInvitesResponse    |
| 22  | RequestOrganizationMemberInvite | POST:/orgs/{OrganizationId}/invites            | RequestOrganizationMemberInviteResponse |
| 23  | UpdateOrganizationMemberInvite  | PUT:/orgs/{OrganizationId}/invites/{UserId}    | UpdateOrganizationMemberInviteResponse  |
| 24  | QueryPosts                      | GET:/posts                                     | QueryResponse<Post>                     |
| 25  | GetPost                         | GET:/posts/{Id}                                | GetPostResponse                         |
| 26  | CreatePost                      | POST:/posts                                    | CreatePostResponse                      |
| 27  | UpdatePost                      | PUT:/posts/{Id}                                | UpdatePostResponse                      |
| 28  | DeletePost                      | DELETE:/posts/{Id}                             | DeletePostResponse                      |
| 29  | LockPost                        | PUT:/posts/{Id}/lock                           |                                         |
| 30  | HidePost                        | PUT:/posts/{Id}/hide                           |                                         |

...

Routes with an associated HTTP Verb, e.g. GET:/technology only allows access with that specific verb, if unspecified any verb can be used.

inspect API

Adding an API Name to the command will let you describe a specific API Endpoint to learn more about its features, restrictions & capabilities, e.g:

x inspect https://techstacks.io LockTechStack

Which will output the APIs description, any tags it was annotated with, its defined routes as well as any Auth Requirements along with all the available Auth Providers registered, e.g:

# LockTechStack
Limit updates to TechStack to Owner or Admin users

Tags:               [TechStacks]
Routes:             /admin/techstacks/{TechnologyStackId}/lock

# Requires Auth
Auth Providers:     twitter (oauth), github (oauth), jwt (Bearer), servicestack (credentials)
Roles:              Admin


# C# DTOs:


using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.Serialization;
using ServiceStack;
using ServiceStack.DataAnnotations;


public partial class LockStackResponse
{
}

///<summary>
///Limit updates to TechStack to Owner or Admin users
///</summary>
[Route("/admin/techstacks/{TechnologyStackId}/lock")]
public partial class LockTechStack
    : IReturn<LockStackResponse>, IPut
{
    [Validate("GreaterThan(0)")]
    public virtual long TechnologyStackId { get; set; }

    public virtual bool IsLocked { get; set; }
}

Whilst the C# code defines the API Service Contract including any user-defined routes, its Response Type, what HTTP Verb it should be called with as well as any declarative validation rules when defined. The properties on the Request DTO define the Typed Inputs that the API Accepts whilst the Response DTO describes what a successful Response will return.

View all Referenced DTOs

Only the Request and Response DTOs representing the APIs Inputs and Outputs are displayed by default, to include all referenced types you can use the IncludeTypes syntax, e.g:

x inspect https://techstacks.io GetTechnology.*

Which will include all referenced types used in this API:

# GetTechnology
Tags:               [Tech]
Routes:             /technology/{Slug}

# C# DTOs:


using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.Serialization;
using ServiceStack;
using ServiceStack.DataAnnotations;


[Route("/technology/{Slug}")]
public partial class GetTechnology
    : IReturn<GetTechnologyResponse>, IRegisterStats, IGet
{
    public virtual string Slug { get; set; }
}

public partial class GetTechnologyResponse
{
    public GetTechnologyResponse()
    {
        TechnologyStacks = new List<TechnologyStack>{};
    }

    public virtual DateTime Created { get; set; }
    public virtual Technology Technology { get; set; }
    public virtual List<TechnologyStack> TechnologyStacks { get; set; }
    public virtual ResponseStatus ResponseStatus { get; set; }
}

public partial interface IRegisterStats
{
}

public partial class Technology
    : TechnologyBase
{
}

public partial class TechnologyBase
{
    public virtual long Id { get; set; }
    public virtual string Name { get; set; }
    public virtual string VendorName { get; set; }
    public virtual string VendorUrl { get; set; }
    public virtual string ProductUrl { get; set; }
    public virtual string LogoUrl { get; set; }
    public virtual string Description { get; set; }
    public virtual DateTime Created { get; set; }
    public virtual string CreatedBy { get; set; }
    public virtual DateTime LastModified { get; set; }
    public virtual string LastModifiedBy { get; set; }
    public virtual string OwnerId { get; set; }
    public virtual string Slug { get; set; }
    public virtual bool LogoApproved { get; set; }
    public virtual bool IsLocked { get; set; }
    public virtual TechnologyTier Tier { get; set; }
    public virtual DateTime? LastStatusUpdate { get; set; }
    public virtual int? OrganizationId { get; set; }
    public virtual long? CommentsPostId { get; set; }
    public virtual int ViewCount { get; set; }
    public virtual int FavCount { get; set; }
}

public partial class TechnologyStack
    : TechnologyStackBase
{
}

public partial class TechnologyStackBase
{
    public virtual long Id { get; set; }
    public virtual string Name { get; set; }
    public virtual string VendorName { get; set; }
    public virtual string Description { get; set; }
    public virtual string AppUrl { get; set; }
    public virtual string ScreenshotUrl { get; set; }
    public virtual DateTime Created { get; set; }
    public virtual string CreatedBy { get; set; }
    public virtual DateTime LastModified { get; set; }
    public virtual string LastModifiedBy { get; set; }
    public virtual bool IsLocked { get; set; }
    public virtual string OwnerId { get; set; }
    public virtual string Slug { get; set; }
    [StringLength(int.MaxValue)]
    public virtual string Details { get; set; }

    [StringLength(int.MaxValue)]
    public virtual string DetailsHtml { get; set; }

    public virtual DateTime? LastStatusUpdate { get; set; }
    public virtual int? OrganizationId { get; set; }
    public virtual long? CommentsPostId { get; set; }
    public virtual int ViewCount { get; set; }
    public virtual int FavCount { get; set; }
}

public enum TechnologyTier
{
    ProgrammingLanguage,
    Client,
    Http,
    Server,
    Data,
    SoftwareInfrastructure,
    OperatingSystem,
    HardwareInfrastructure,
    ThirdPartyServices,
}

Thanks to ServiceStack's unique message-based design the code contract used to define the Service is also all that's needed to invoke the API along with the generic ServiceStack Client library which for .NET is available in the ServiceStack.Client NuGet package:

dotnet add package ServiceStack.Client

Which together with the above C# DTOs enables its optimal end-to-end typed API:

var client = new JsonServiceClient("https://techstacks.io");

client.Send(new LockTechStack { TechnologyStackId = id, IsLocked = true });

Request DTOs annotated with an IVerb interface marker (e.g. IPut) can instead use Send() to invoke the API with that HTTP Verb.

This same simplified usage scenario is also available in each of Add ServiceStack Reference supported Languages:

Where the -lang option can be used to change what language to return the DTO Types in:

Usage: x inspect <base-url> <request> -lang <csharp|python|typescript|dart|java|kotlin|swift|fsharp|vbnet>
       x inspect <base-url> <request> -lang <cs|py|ts|da|ja|kt|sw|fs|vb>

For example to view the DTOs in Swift run:

x inspect https://techstacks.io LockTechStack -lang swift

Output:

# LockTechStack
Limit updates to TechStack to Owner or Admin users

Tags:               [TechStacks]
Routes:             /admin/techstacks/{TechnologyStackId}/lock

# Requires Auth
Auth Providers:     twitter (oauth), github (oauth), jwt (Bearer), servicestack (credentials)
Roles:              Admin


# Swift DTOs:


import Foundation
import ServiceStack

/**
* Limit updates to TechStack to Owner or Admin users
*/
// @Route("/admin/techstacks/{TechnologyStackId}/lock")
public class LockTechStack : IReturn, IPut, Codable
{
    public typealias Return = LockStackResponse

    // @Validate(Validator="GreaterThan(0)")
    public var technologyStackId:Int?

    public var isLocked:Bool?

    required public init(){}
}

public class LockStackResponse : Codable
{
    required public init(){}
}

send - Invoking APIs

In addition to being able to invoke ServiceStack APIs natively in each supported language, they can also be invoked with just a single command-line.

Running x send will display different example usage of this versatile tool which supports most of ServiceStack's Authentication options:

Usage: x <send|GET|POST|PUT|DELETE|PATCH> <base-url> <request>
       x <send|GET|POST|PUT|DELETE|PATCH> <base-url> <request> {js-object}
       x <send|GET|POST|PUT|DELETE|PATCH> <base-url> <request> < body.json

Options:
 -raw                   Show raw HTTP Headers and Body
 -json                  Show Body as JSON
 -token <token>         Use JWT or API Key Bearer Token
 -basic <user:pass>     Use HTTP Basic Auth
 -authsecret <secret>   Use Admin Auth Secret
 -ss-id <session-id>    Use ss-id Session Id Cookie
 -cookies <file>        Store and Load Cookies from file

HTTP APIs can be invoked with a specific HTTP Verb or send which will use the APIs preferred HTTP Method when it can be inferred e.g. Request DTO is annotated with IVerb interface marker or its implementation uses a HTTP Verb method name instead of Any().

UI for generating post commands

Whilst the syntax for invoking APIs from the command line should be fairly intuitive, you may benefit from using the UI in apps.servicestack.net to craft the initial API call that can be copied by clicking the copy icon from Invoke API from command line command:

This is quick way for using a UI to bootstrap the initial post command that you can continue iterating on and invoking locally.

INFO

apps.servicestack.net requires your remote ServiceStack App is accessible from the Internet

Invoking APIs without arguments

APIs that don't require arguments can be invoked with just their names, e.g. we can invoke the GetLocations Covid 19 Vaccine Watch API with either:

x send https://covid-vac-watch.netcore.io GetLocations
x GET https://covid-vac-watch.netcore.io GetLocations

Output:

locations:
  Alabama
  Alaska
  American Samoa
  Arizona
  Arkansas
  Bureau of Prisons
  California
  Colorado
  Connecticut
  Delaware
  Dept of Defense
  District of Columbia
  Federated States of Micronesia
  Florida
  Georgia
  ...

By default APIs return a human friendly text output optimal for reading at a glance.

json Response output

Alternatively use -json if you're only interested in viewing the JSON response, e.g:

x send https://covid-vac-watch.netcore.io GetLocations -json

Output:

{"locations":["Alabama","Alaska","American Samoa","Arizona","Arkansas","Bureau of Prisons","California","Colorado","Connecticut","Delaware","Dept of Defense","District of Columbia","Federated States of Micronesia","Florida","Georgia","Guam","Hawaii","Idaho","Illinois","Indian Health Svc","Indiana","Iowa","Kansas","Kentucky","Long Term Care","Louisiana","Maine","Marshall Islands","Maryland","Massachusetts","Michigan","Minnesota","Mississippi","Missouri","Montana","Nebraska","Nevada","New Hampshire","New Jersey","New Mexico","New York State","North Carolina","North Dakota","Northern Mariana Islands","Ohio","Oklahoma","Oregon","Pennsylvania","Puerto Rico","Republic of Palau","Rhode Island","South Carolina","South Dakota","Tennessee","Texas","United States","Utah","Vermont","Veterans Health","Virgin Islands","Virginia","Washington","West Virginia","Wisconsin","Wyoming"]}

This is useful if you want to capture the results in a .json text file for inspection with other JSON aware tools:

x send https://covid-vac-watch.netcore.io GetLocations -json > results.json

jq is a popular command-line tool for querying JSON outputs that's useful for inspecting the JSON response of one command to chain using it in others. A useful usecase is for parse an AuthenticateResponse to capture the JWT bearerToken property with jq -r .bearerToken:

TOKEN=$(x send https://test.servicestack.net Authenticate "{provider:'credentials',username:'admin',password:'test'}" -json | jq -r .bearerToken)

Then using it to make stateless Authenticated requests, e.g:

x send -token $TOKEN https://test.servicestack.net HelloSecure "{name:'World'}"

Output:

result:  Hello, World!

raw HTTP output

If preferred you can instead view the full HTTP Response including HTTP Headers by adding the -raw flag, e.g:

x send https://covid-vac-watch.netcore.io GetLocations -raw

Output:

GET /json/reply/GetLocations HTTP/1.1
Host: covid-vac-watch.netcore.io
Accept: application/json

HTTP/1.1 200 OK
Server: nginx/1.19.3
Date: Wed, 28 Jul 2021 07:39:34 GMT
Transfer-Encoding: chunked
Connection: keep-alive
Vary: Accept
X-Powered-By: ServiceStack/5.111 NetCore/Linux
Strict-Transport-Security: max-age=31536000
Content-Type: application/json; charset=utf-8

{"locations":["Alabama","Alaska","American Samoa","Arizona","Arkansas","Bureau of Prisons","California","Colorado","Connecticut","Delaware","Dept of Defense","District of Columbia","Federated States of Micronesia","Florida","Georgia","Guam","Hawaii","Idaho","Illinois","Indian Health Svc","Indiana","Iowa","Kansas","Kentucky","Long Term Care","Louisiana","Maine","Marshall Islands","Maryland","Massachusetts","Michigan","Minnesota","Mississippi","Missouri","Montana","Nebraska","Nevada","New Hampshire","New Jersey","New Mexico","New York State","North Carolina","North Dakota","Northern Mariana Islands","Ohio","Oklahoma","Oregon","Pennsylvania","Puerto Rico","Republic of Palau","Rhode Island","South Carolina","South Dakota","Tennessee","Texas","United States","Utah","Vermont","Veterans Health","Virgin Islands","Virginia","Washington","West Virginia","Wisconsin","Wyoming"]}

Using Rider HTTP Scratch Tools

A productive way to further re-iterate on the HTTP Request is to copy+paste the HTTP Request Header into JetBrains Rider’s HTTP Rest tool where you can run it and customize it using their text editor UI by going to New Scratch File and selecting HTTP Request or via Tools > HTTP Client > Create Request in HTTP Client:

Invoking APIs with Arguments

To invoke an API with arguments we can use a JavaScript Object Literal which allows a wrist-friendly syntax for invoking any API including rich AutoQuery APIs which thanks to its human friendly output allows quickly inferring Query result-sets from a glance, e.g:

Quote Arguments in Unix Shells

Since JavaScript operators have special meaning in Unix shells you'd need to wrap the object literal in double quotes to have the shell pass it verbatim to the command tool without evaluating it, e.g:

Windows / Linux / macOS:

x send https://techstacks.io FindTechnologies "{Ids:[1,2,6],VendorName:'Google',Take:1}"

Windows Only:

x send https://techstacks.io FindTechnologies {Ids:[1,2,6],VendorName:'Google',Take:1}

So requests that doesn't use any special batch characters can be sent with or without quotes. An alternative way to by pass the shell is to redirect a JSON Request body instead, e.g:

x send https://techstacks.io FindTechnologies < FindTechnologies.json

Last 5 Recorded Dates of Vaccinated people in Alaska

x send https://covid-vac-watch.netcore.io QueryVaccinationRates "{Location:'Alaska',orderBy:'-date',take:5,Fields:'id,date,peopleVaccinated',include:'total'}"

Output:

offset:   0
total:    195

results:
| # | id    | date                        | peopleVaccinated |
|---|-------|-----------------------------|------------------|
| 1 | 12308 | 2021-07-25T00:00:00.0000000 |           372940 |
| 2 | 12307 | 2021-07-24T00:00:00.0000000 |           372902 |
| 3 | 12306 | 2021-07-23T00:00:00.0000000 |           372132 |
| 4 | 12305 | 2021-07-22T00:00:00.0000000 |           371514 |
| 5 | 12304 | 2021-07-21T00:00:00.0000000 |           371062 |

Multi conditional TechStacks query

x send https://techstacks.io FindTechnologies "{Ids:[1,2,6],VendorName:'Google',Take:10,Fields:'Id,Name,VendorName,Tier,FavCount,ViewCount'}"

Output:

offset:   0
total:    18

results:
| #  | id | name                   | vendorName   | tier                   | viewCount | favCount |
|----|----|------------------------|--------------|------------------------|-----------|----------|
| 1  |  1 | ServiceStack           | ServiceStack | Server                 |      4204 |        5 |
| 2  |  2 | PostgreSQL             | PostgreSQL   | Data                   |      2291 |        4 |
| 3  |  6 | AWS RDS                | Amazon       | Data                   |       625 |        1 |
| 4  |  7 | AngularJS              | Google       | Client                 |      5012 |        1 |
| 5  | 13 | Google Closure Library | Google       | Client                 |       390 |        1 |
| 6  | 15 | Dart                   | Google       | ProgrammingLanguage    |       320 |        2 |
| 7  | 18 | Go                     | Google       | ProgrammingLanguage    |      3865 |        2 |
| 8  | 57 | LevelDB                | Google       | Data                   |       325 |        1 |
| 9  | 61 | Firebase               | Google       | Data                   |       722 |        1 |
| 10 | 72 | Google Cloud Platform  | Google       | HardwareInfrastructure |       269 |        1 |

Invoking Complex APIs

As ServiceStack APIs supports nested complex types in query strings the JS Object Request body will be able to scale to execute even deeply complicated API requests in both HTTP Methods without Request Bodies, e.g:

Example GET Request

x GET https://test.servicestack.net StoreLogs "{Loggers:[{Id:786,Devices:[{Id:5955,Type:'Panel',TimeStamp:1,Channels:[{Name:'Temperature',Value:'58'},{Name:'Status',Value:'On'}]}]}]}" -raw

Where they're sent on the query string:

GET /json/reply/StoreLogs?Loggers=%5b%7bId%3a786,Devices%3a%5b%7bId%3a5955,Type%3aPanel,TimeStamp%3a1,Channels%3a%5b%7bName%3aTemperature,Value%3a58%7d,%7bName%3aStatus,Value%3aOn%7d%5d%7d%5d%7d%5d HTTP/1.1
Host: test.servicestack.net
Accept: application/json

HTTP/1.1 200 OK
Server: nginx/1.18.0, (Ubuntu)
Date: Wed, 28 Jul 2021 07:40:26 GMT
Transfer-Encoding: chunked
Connection: keep-alive
Set-Cookie: ss-id=nt6W8DDHatjwf0kToEUa; path=/; samesite=strict; httponly, ss-pid=GsP8tmccacQn0fH8vOAD; expires=Sun, 28 Jul 2041 07:40:26 GMT; path=/; samesite=strict; httponly
Vary: Accept
Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: Content-Type, Allow, Authorization, X-Args
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, PATCH, OPTIONS, HEAD
X-Powered-By: ServiceStack/5.111 NetCore/Linux
Content-Type: application/json; charset=utf-8

{"existingLogs":[{"id":786,"devices":[{"id":5955,"type":"Panel","timeStamp":1,"channels":[{"name":"Temperature","value":"58"},{"name":"Status","value":"On"}]}]}]}

Example POST Request

As well as HTTP Requests with Request bodies where only the method used needs to change whilst the Request JS Object literal stays exactly the same, e.g:

x POST https://test.servicestack.net StoreLogs "{Loggers:[{Id:786,Devices:[{Id:5955,Type:'Panel',TimeStamp:1,Channels:[{Name:'Temperature',Value:'58'},{Name:'Status',Value:'On'}]}]}]}" -raw

Where instead of being sent on the query string it's posted inside a JSON Request body, irrespective of how its sent a ServiceStack API supporting any HTTP Method by being implemented with the Any() method name will result in an identical response:

POST /json/reply/StoreLogs HTTP/1.1
Host: test.servicestack.net
Accept: application/json
Content-Type: application/json
Content-Length: 157

{"Loggers":[{"Id":786,"Devices":[{"Id":5955,"Type":"Panel","TimeStamp":1,"Channels":[{"Name":"Temperature","Value":"58"},{"Name":"Status","Value":"On"}]}]}]}

HTTP/1.1 200 OK
Server: nginx/1.18.0, (Ubuntu)
Date: Wed, 28 Jul 2021 07:40:54 GMT
Transfer-Encoding: chunked
Connection: keep-alive
Set-Cookie: ss-id=X1BmXXrr9vr5DIpAoxFM; path=/; samesite=strict; httponly, ss-pid=J8kDBiJ37WEOnywRdGMS; expires=Sun, 28 Jul 2041 07:40:54 GMT; path=/; samesite=strict; httponly
Vary: Accept
Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: Content-Type, Allow, Authorization, X-Args
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, PATCH, OPTIONS, HEAD
X-Powered-By: ServiceStack/5.111 NetCore/Linux
Content-Type: application/json; charset=utf-8

{"existingLogs":[{"id":786,"devices":[{"id":5955,"type":"Panel","timeStamp":1,"channels":[{"name":"Temperature","value":"58"},{"name":"Status","value":"On"}]}]}]}

Redirected Input Example

For requests that get significantly large it may be more convenient to maintain the request body in a separate file that you can pipe into the command instead, e.g:

x send https://test.servicestack.net StoreLogs -raw < StoreLogs.json

Output:

POST /json/reply/StoreLogs HTTP/1.1
Host: test.servicestack.net
Accept: application/json
Content-Type: application/json
Content-Length: 157

{"Loggers":[{"Id":786,"Devices":[{"Id":5955,"Type":"Panel","TimeStamp":1,"Channels":[{"Name":"Temperature","Value":"58"},{"Name":"Status","Value":"On"}]}]}]}

HTTP/1.1 200 OK
Server: nginx/1.18.0, (Ubuntu)
Date: Wed, 28 Jul 2021 07:41:38 GMT
Transfer-Encoding: chunked
Connection: keep-alive
Set-Cookie: ss-id=kScY3mYF06e3iuPaCnaD; path=/; samesite=strict; httponly, ss-pid=hpbimtl9dIWA1IbJPMXN; expires=Sun, 28 Jul 2041 07:41:38 GMT; path=/; samesite=strict; httponly
Vary: Accept
Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: Content-Type, Allow, Authorization, X-Args
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, PATCH, OPTIONS, HEAD
X-Powered-By: ServiceStack/5.111 NetCore/Linux
Content-Type: application/json; charset=utf-8

{"existingLogs":[{"id":786,"devices":[{"id":5955,"type":"Panel","timeStamp":1,"channels":[{"name":"Temperature","value":"58"},{"name":"Status","value":"On"}]}]}]}

Remove the -raw option to display the response in a more human-friendly readable format:

x send https://test.servicestack.net StoreLogs < StoreLogs.json

Output:

[existingLogs]
id:       786

[devices]
id:         5955
type:       Panel
timeStamp:  1

channels:
| # | name        | value |
|---|-------------|-------|
| 1 | Temperature | 58    |
| 2 | Status      | On    |

Authentication

To support making Authenticated Requests most of ServiceStack's built-in Authentication Options are supported from the options below:

Options:
 -token <token>         Use JWT or API Key Bearer Token
 -basic <user:pass>     Use HTTP Basic Auth
 -authsecret <secret>   Use Admin Auth Secret
 -ss-id <session-id>    Use ss-id Session Id Cookie
 -cookies <file>        Store and Load Cookies from file

Since Username/Password Credentials Auth is a normal ServiceStack API we can invoke it like normal, e.g:

x send https://test.servicestack.net Authenticate "{provider:'credentials',username:'admin',password:'test'}"

However to hide your credentials from command history logs you'll likely want to maintain your credentials in a separate file, e.g:

x send https://test.servicestack.net Authenticate < auth.json

Which if successful will return a populated human-friendly AuthenticateResponse:

userId:          2
sessionId:       QfJLhmd8XQxeuAIqvoCY
userName:        admin
displayName:     admin DisplayName
bearerToken:     eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsImtpZCI6IjNuLyJ9.eyJzdWIiOjIsImlhdCI6MTYyNzM4MjY5NiwiZXhwIjoxNjI4NTkyMjk2LCJlbWFpbCI6ImFkbWluQGdtYWlsLmNvbSIsImdpdmVuX25hbWUiOiJGaXJzdCBhZG1pbiIsImZhbWlseV9uYW1lIjoiTGFzdCBhZG1pbiIsIm5hbWUiOiJhZG1pbiBEaXNwbGF5TmFtZSIsInByZWZlcnJlZF91c2VybmFtZSI6ImFkbWluIiwicm9sZXMiOlsiQWRtaW4iXSwianRpIjozfQ.j80f1KYsNRDhygO817NSaqYg7DIR1ptLZQUB1mZd_R8
refreshToken:    eyJ0eXAiOiJKV1RSIiwiYWxnIjoiSFMyNTYiLCJraWQiOiIzbi8ifQ.eyJzdWIiOjIsImlhdCI6MTYyNzM4MjY5NiwiZXhwIjoxNjU4OTE4Njk2LCJqdGkiOi0zfQ.nkwDYvmB5_QHm6hmVv8Thfl2Iz8W_LUDf6bspb-Nu2c
profileUrl:      data:image/svg+xml,%3Csvg width='100' height='100' viewBox='0 0 100 100' xmlns='http://www.w3.org/2000/svg'%3E %3Cstyle%3E .path%7B%7D %3C/style%3E %3Cg id='male-svg'%3E%3Cpath fill='%23556080' d='M1 92.84V84.14C1 84.14 2.38 78.81 8.81 77.16C8.81 77.16 19.16 73.37 27.26 69.85C31.46 68.02 32.36 66.93 36.59 65.06C36.59 65.06 37.03 62.9 36.87 61.6H40.18C40.18 61.6 40.93 62.05 40.18 56.94C40.18 56.94 35.63 55.78 35.45 47.66C35.45 47.66 32.41 48.68 32.22 43.76C32.1 40.42 29.52 37.52 33.23 35.12L31.35 30.02C31.35 30.02 28.08 9.51 38.95 12.54C34.36 7.06 64.93 1.59 66.91 18.96C66.91 18.96 68.33 28.35 66.91 34.77C66.91 34.77 71.38 34.25 68.39 42.84C68.39 42.84 66.75 49.01 64.23 47.62C64.23 47.62 64.65 55.43 60.68 56.76C60.68 56.76 60.96 60.92 60.96 61.2L64.74 61.76C64.74 61.76 64.17 65.16 64.84 65.54C64.84 65.54 69.32 68.61 74.66 69.98C84.96 72.62 97.96 77.16 97.96 81.13C97.96 81.13 99 86.42 99 92.85L1 92.84Z'/%3E%3C/g%3E%3C/svg%3E

[roles]
Admin

Authentication -cookies

Likely the easiest and most versatile authentication option would be to use a separate cookies file where it will load and save cookies after each request allowing each request to be made within the context of the same authenticated session as done in browsers, e.g:

x send -cookies cookies.xml https://test.servicestack.net Authenticate < auth.json

We can test that it's working by first trying to call an Authentication protected Service without any Authentication options, e.g:

x send https://test.servicestack.net HelloSecure "{name:'World'}"

Output:

The remote server returned an error: (401) Not Authenticated.

Then re-trying the request, providing the cookies.xml that was populated after the success Authentication:

x send -cookies cookies.xml https://test.servicestack.net HelloSecure "{name:'World'}"

Output:

result:  Hello, World!

The -cookies Authentication option is the most versatile as it supports standard Server Sessions as well as stateless Authentication options like JWT Auth configured with Token Cookies where the JWT Bearer Token is maintained in a cookie.

Authentication -token

The -token Authentication option should be used when authenticating with Bearer Tokens like JWT or API Key Auth Providers. When the JwtAuthProvider is configured a successful Authentication Response will return a populated bearerToken which we can use to authenticate with instead. As Bearer Tokens can become quite verbose you may want to choose to save in a shell environment variable instead, e.g:

Windows:

set TOKEN=...
x send -token %TOKEN% https://test.servicestack.net HelloSecure {name:'World'}

Linux / macOS:

TOKEN=...
x send -token $TOKEN https://test.servicestack.net HelloSecure "{name:'World'}"

Output:

result:  Hello, World!

Capturing bearerToken with #Script expression

A dependency-free solution for capturing the bearerToken is to utilize the #Script eval expression support in x to make an API Request and parsing the JSON response and parsing it with #Script methods, e.g:

TOKEN=$(x -e "'https://test.servicestack.net/auth' |> urlTextContents({method:'POST',accept:'application/json',data:'provider=credentials&username=admin&password=test'}) |> parseJson |> get('bearerToken')")

Then using it to make stateless Authenticated requests, e.g:

x send -token $TOKEN https://test.servicestack.net HelloSecure "{name:'World'}"

Capturing bearerToken with jq

jq is a versatile command for extracting info from JSON outputs which can extract the raw string "bearerToken" property value of an AuthenticateResponse with jq -r .bearerToken, e.g:

TOKEN=$(x send https://test.servicestack.net Authenticate "{provider:'credentials',username:'admin',password:'test'}" -json | jq -r .bearerToken)

Inspect JWTs

When working with JWTs it can be useful to quickly inspect its contents which we can do with inspect-jwt:

Usage: x inspect-jwt <jwt>
       x inspect-jwt < file.txt

Which we can use with the populated bearerToken above to inspect the contents of the JWT in a human-friendly format, e.g:

$ x inspect-jwt eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsImtpZCI6IjNuLyJ9.eyJzdWIiOjIsImlhdCI6MTYyNjg0OTEzMCwiZXhwIjoxNjI4MDU4NzMwLCJlbWFpbCI6ImFkbWluQGdtYWlsLmNvbSIsImdpdmVuX25hbWUiOiJGaXJzdCBhZG1pbiIsImZhbWlseV9uYW1lIjoiTGFzdCBhZG1pbiIsIm5hbWUiOiJhZG1pbiBEaXNwbGF5TmFtZSIsInByZWZlcnJlZF91c2VybmFtZSI6ImFkbWluIiwicm9sZXMiOlsiQWRtaW4iXSwianRpIjo5fQ.uLp_QkmBo6J6TlXiUPl0Iq6TkTbF0xzncbUI1HmDro4

Output:

[JWT Header]
typ:  JWT
alg:  HS256
kid:  3n/


[JWT Payload]
sub:                 2
iat:                 1626849130 (Wed, 21 Jul 2021 06:32:10 GMT)
exp:                 1628058730 (Wed, 04 Aug 2021 06:32:10 GMT)
email:               admin@gmail.com
given_name:          First admin
family_name:         Last admin
name:                admin DisplayName
preferred_username:  admin

[roles]
Admin

jti:                 9

Authentication -basic

When the BasicAuthProvider is configured we can authenticate with HTTP Basic Auth using the -basic command line option which supports both clear text:

x send -basic admin:test https://test.servicestack.net HelloSecure "{name:'World'}"

Output:

x send -basic admin:test https://test.servicestack.net HelloSecure "{name:'World'}"

As well as Base64 encoded credentials which we can convert using the x base64 tool, e.g:

x base64 admin:test

Output:

YWRtaW46dGVzdA==

x send -basic YWRtaW46dGVzdA== https://test.servicestack.net HelloSecure "{name:'World'}"

Output:

result:  Hello, World!

Although a Base64 encoded password does not offer much protection for your password (e.g. it can be decoded with x unbase64 YWRtaW46dGVzdA==), to avoid your password from being captured in shell command history we can instead read it from a plain text file, e.g:

set /P basic=<credentials.txt
x send -basic %basic% https://test.servicestack.net HelloSecure "{name:'World'}"

Output:

result:  Hello, World!

Authentication -authsecret

If the remote ServiceStack App is configured with an Admin Auth Secret, i.e:

SetConfig(new HostConfig { AdminAuthSecret = "secretz" });

It can be used to authenticated with using the -authsecret option:

x send -authsecret secretz https://test.servicestack.net HelloSecure "{name:'World'}"

Output:

result:  Hello, World!

Authentication -ss-id

When the remote ServiceStack App is configured to use Server Sessions you can impersonate a pre-authenticated Users Session using the -ss-id Authentication option which is useful for impersonating an existing Users Session by copying their ss-id or ss-pid Cookie which you can find in Web Inspector's Application page:

If Users were authenticated with Remember Me checked their Session will be stored against the ss-pid Cookie otherwise it will use the ss-id Session Cookie.

Making a GET request to the Authenticate API is another way you can test which user you're authenticated as, e.g:

x GET -ss-id FoCHJK9Apl9mrcaq3ceE https://blazor-vue.web-templates.io Authenticate

Output:

userId:          1
sessionId:       FoCHJK9Apl9mrcaq3ceE
userName:        admin@email.com
displayName:     Admin User
profileUrl:      data:image/svg+xml,%3Csvg width='100' height='100' viewBox='0 0 100 100' xmlns='http://www.w3.org/2000/svg'%3E %3Cstyle%3E .path%7B%7D %3C/style%3E %3Cg id='male-svg'%3E%3Cpath fill='%23556080' d='M1 92.84V84.14C1 84.14 2.38 78.81 8.81 77.16C8.81 77.16 19.16 73.37 27.26 69.85C31.46 68.02 32.36 66.93 36.59 65.06C36.59 65.06 37.03 62.9 36.87 61.6H40.18C40.18 61.6 40.93 62.05 40.18 56.94C40.18 56.94 35.63 55.78 35.45 47.66C35.45 47.66 32.41 48.68 32.22 43.76C32.1 40.42 29.52 37.52 33.23 35.12L31.35 30.02C31.35 30.02 28.08 9.51 38.95 12.54C34.36 7.06 64.93 1.59 66.91 18.96C66.91 18.96 68.33 28.35 66.91 34.77C66.91 34.77 71.38 34.25 68.39 42.84C68.39 42.84 66.75 49.01 64.23 47.62C64.23 47.62 64.65 55.43 60.68 56.76C60.68 56.76 60.96 60.92 60.96 61.2L64.74 61.76C64.74 61.76 64.17 65.16 64.84 65.54C64.84 65.54 69.32 68.61 74.66 69.98C84.96 72.62 97.96 77.16 97.96 81.13C97.96 81.13 99 86.42 99 92.85L1 92.84Z'/%3E%3C/g%3E%3C/svg%3E

[roles]
Admin