Docs

"It's not done until the docs are great." - Evan Czaplicki

Documentation is coming!

When Jangle is released, these docs will be an official reference to what's available.

Here's a preview of how that might look.

Overview

When it comes to customizing Jangle, we don't write code, we provide data.

models:
  Author:
    name: text
    email: email?
  BlogPost:
    title: text
    date: date
    authors: Author[]
    content: richText

Models

Every model supports three main properties:

  1. type - The type of model:
    • lists - a collection of many items, like blog posts or authors.
    • item - a single item, like a homepage or site settings.
    • header - labels to help organize content visually.
  2. options - customizations for the model
    • slug
    • labels.plural (for lists)
    • labels.singular (for lists)
    • label (for items/headers)
  3. fields - the fields within the model.

Shorthand

Jangle doesn't encourage us to list out these three properties for each model.

1. Lists

Making lists is really common, so it's idiomatic to use the following shorthand when describing them:

models:
  Author:
    name: text
    email: email?

Behind the scenes, Jangle will expand out that Author model like this:



 
 
 



models:
  Author:
    type: list
    options: {}
    fields:
      name: text
      email: email?

There's no need to type out the highlighted lines over and over. Describe what makes your model unique!

2. Headers

Headers are just a single line of text, so that's all you need to type:


 







models:
  'Blog Section':
  Author:
    name: text
    email: email?
  BlogPost:
    title: text
    content: richText

Jangle will expand out our "Blog Section" header for us:


 
 
 
 


models:
  'Blog Section':
    type: header
    options:
      label: Blog Section
  # ...

Three lines of typing saved (whew, close one)!

Lists

list


 
 
 

models:
  Author:
    name: text
    email: email?

Items

item


 
 
 
 
 
 
 
 
 

models:
  Homepage:
    type: item
    fields:
      title:
        type: text
        default: 'Jangle'
      description:
        type: text
        default: 'a cms for humans.'

Headers

header


 









models:
  'Blog Section':
  Author:
    name: text
    email: email?
  BlogPost:
    title: text
    date: date
    authors: Author[]
    content: richText

Fields

Fields are the properties of models that can store content.

Shorthand

Just like models, fields support shorthand for these common patterns.

1. Optionality

In Jangle, fields are required unless otherwise specified.

Using the ? at the end of a field type is the easiest way to specify a field is optional:




 

models:
  Author:
    name: text
    email: email?

Jangle will expand out those fields like this:





 


 

models:
  Author:
    name:
      type: text
      required: true
    email:
      email: email
      required: false

2. Lists

Sometimes you want one thing, sometimes you want a list of things.

Use [] at the end of a field type to say you'd like it to be a list of those values:




 


models:
  BlogPost:
    title: text
    tags: text[]
    content: richText

Jangle will expand out tags into a list for us:




 
 
 


models:
  BlogPost:
    title: text
    tags:
      type: list
      field: text
    content: richText

3. Relationships

Every relationship relates to a specific model.

That's why Jangle allows you to call out the name of model specifically:




 

models:
  BlogPost:
    title: text
    author: Author

The capital letter tells Jangle, you want to relate to Author items:




 
 
 

models:
  BlogPost:
    title: text
    author:
      type: relationship
      ref: Author

4. Groups

Another common pattern is to use a group field to organize content together.

If Jangle doesn't see type as the first item under a field, the group shorthand will kick in:




 
 
 
 

models:
  Author:
    name: text
    contact:
      email: email
      phone: phone
      fax: phone

Jangle will expand out the contact field into a group for us:




 
 
 
 
 
 

models:
  Author:
    name: text
    contact:
      type: group
      fields:
        email: email
        phone: phone
        fax: phone

Text Field

text, email, phone, number

Configuration



 
 

models:
  Author:
    name: text
    favoriteNumber: number

Screenshot

Screenshot coming soon!

Here's an emoji while you wait: 🐶

Data



 
 


{
  _id: Id(...),
  name: 'Ryan',
  favoriteNumber: 7
}

Rich Text Field

richText

Configuration




 

models:
  BlogPost:
    title: text
    content: richText

Screenshot

Screenshot coming soon!

Here's an emoji while you wait: 🐔

Data




 


{
  _id: Id(...),
  title: 'Jangle is neat!',
  content: '## What a CMS.\nHumans love it, who are _we_ to argue...'
}

Choice Field

choice

Configuration




 
 
 
 
 
 

models:
  Author:
    name: text
    favoriteColor:
      type: choice
      options:
        red: Red
        blue: Super Blue
        green: Green

Screenshot

Screenshot coming soon!

Here's an emoji while you wait: 🐴

Data




 


{
  _id: Id(...),
  title: 'Erik',
  favoriteColor: 'blue'
}

Relationship Field

relationship

Configuration




 

models:
  BlogPost:
    title: text
    author: Author

Screenshot

Screenshot coming soon!

Here's an emoji while you wait: 🐒

Data




 


{
  _id: Id(...),
  title: 'Robot Slips on Banana Peel',
  author: Id(...)
}

Date Field

date, time, datetime

Configuration




 

models:
  BlogPost:
    title: text
    date: datetime

Screenshot

Screenshot coming soon!

Here's an emoji while you wait: 🐷

Data




 


{
  _id: Id(...),
  title: 'Dog chases mailman.',
  date: '2019-04-18T05:39:30.442Z'
}

Image Field

image

Configuration




 

models:
  BlogPost:
    title: text
    featuredImage: image

Screenshot

Screenshot coming soon!

Here's an emoji while you wait: 🦊

Data




 


{
  _id: Id(...),
  title: 'Rasperry Pi eaten by confused technologist',
  featuredImage: 'https://res.cloudinary.com/jangle/image/upload/v1526917235/ilrw0n1exxqkfnchg07a.jpg'
}

File Field

file

Configuration




 

models:
  BlogPost:
    title: text
    pdf: file

Screenshot

Screenshot coming soon!

Here's an emoji while you wait: 🐹

Data




 


{
  _id: Id(...),
  title: 'Everybody loves a good PDF.',
  pdf: 'https://res.cloudinary.com/jangle/raw/upload/v1691723525/nchgxx07ailrw0n1eqkf.pdf'
}

List Field

list

Configuration




 

models:
  BlogPost:
    title: text
    tags: text[]

Screenshot

Screenshot coming soon!

Here's an emoji while you wait: 🐼

Data




 
 
 
 
 


{
  _id: Id(...),
  title: 'Using Elm on the backend.',
  tags: [
    'elm',
    'nodejs',
    'web'
  ]
}

Group Field

group

Configuration



 
 
 
 

models:
  Author:
    name:
      first: text
      middle: text?
      last: text

Screenshot

Screenshot coming soon!

Here's an emoji while you wait: 🦋

Data



 
 
 
 



{
  _id: Id(...),
  name: {
    first: 'Ryan',
    middle: 'Nicholas',
    last: 'Haskell-Glatz'
  }
}

Blocks Field

blocks

Configuration



 
 
 
 
 
 
 
 
 
 

models:
  Page:
    sections:
      type: blocks
      fields:
        hero:
          title: text
          description: text?
        featureSection:
          title: text
          features: text[]
        testimonial: Testimonial

Screenshot

Screenshot coming soon!

Here's an emoji while you wait: 🐢

Data



 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 


{
  _id: Id(...),
  sections: [
    {
      type: 'hero',
      data: {
        title: 'Jangle',
        description: 'a cms for humans.'
      }
    },
    {
      type: 'testimonial',
      data: Id(...)
    },
    {
      type: 'testimonial',
      data: Id(...)
    },
    {
      type: 'featureSection',
      data: {
        title: 'Features',
        features: [
          'Authentication',
          'History',
          'Publishing'
        ]
      }
    }
  ]
}

REST Endpoints

After you provide your configuration, Jangle will automatically generate a REST API.

Jangle uses REST verbs to promise the following:

  • GET - Never updates content, read-only.
  • POST - Updates content, calling many times will make many updates.
  • PUT - Updates content, calling many times has same effect as calling once.
  • DELETE - Removes an item.

Common Errors

Some fields require a body or user tokens, so you might encounter errors like these:

Missing Body 400

POST /api/lists/authors
Authorization: Bearer <good-token>
{
  message: 'This request requires a JSON body.',
  link: 'https://www.jangle.io/errors/#no-body-provided'
}

Missing User Token 401

POST /api/lists/authors
Body: { "name": "Jimmy", posts: [] }
{
  message: 'This request requires a user token.',
  link: 'https://www.jangle.io/errors/#user-token-required'
}

Bad User Token 403

POST /api/lists/authors
Authorization: Bearer <bad-token>
Body: { "name": "Jimmy", posts: [] }
{
  message: "This user token is not valid.",
  link: 'https://www.jangle.io/errors/#invalid-user-token'
}

List API

Overview

Name Route Protected?
Find GET /api/lists/:name Public
Get GET /api/lists/:name/:id Public
Create POST /api/lists/:name Protected
Update PUT /api/lists/:name/:id Protected
Remove DELETE /api/lists/:name/:id Protected
Publish PUT /api/lists/:name/:id/publish Protected
Unpublish PUT /api/lists/:name/:id/unpublish Protected
Preview GET /api/lists/:name/:id/preview/:version Protected
Restore PUT /api/lists/:name/:id/restore/:version Protected

List API - Find

Finds items in the name list.

GET /api/lists/:name

Optional Parameters

Name Description Example
count If true, returns the number of items. ?count=true
limit The maximum number of items to retrieve. ?limit=25
populate Related fields to expand (instead of returning the id). ?populate=author
select The fields you want to retrive ?select=50
skip The number of items to skip. ?skip=50
sort Fields to sort by. ?sort=name
where Filters based on a query. ?where={"name":"Ryan"}
status Select protected content, like drafts or trash. ?status=draft

Example 200

GET /api/lists/authors

[
  { _id: 1, name: 'Ryan', /* ... */ },
  { _id: 2, name: 'Erik', /* ... */ },
  { _id: 3, name: 'Alexa', /* ... */ }
]

Example (count) 200

GET /api/lists/authors?count=true

3

Example (limit) 200

GET /api/lists/authors?limit=2

[
  { _id: 1, name: 'Ryan', /* ... */ },
  { _id: 2, name: 'Erik', /* ... */ }
]

Example (populate) 200

GET /api/lists/authors?populate=posts

[
  { _id: 1,
    name: 'Ryan',
    posts: [
      { _id: 4, title: 'Introducing Jangle', /* ... */ },
      { _id: 5, title: 'Elm is Fun', /* ... */ }
    ],
    /* ... */
  },
  { _id: 2,
    name: 'Erik',
    posts: [
      { _id: 6, title: 'REST APIs are neat.', /* ... */ }
    ],
    /* ... */
  },
  { _id: 3,
    name: 'Alexa',
    posts: [],
    /* ... */
  }
]

Example (skip) 200

GET /api/lists/authors?skip=1

[
  { _id: 2, name: 'Erik', /* ... */ },
  { _id: 3, name: 'Alexa', /* ... */ }
]

Example (select) 200

GET /api/lists/authors?select=name,posts

[
  { _id: 1, name: 'Ryan', posts: [ 4, 5 ] },
  { _id: 2, name: 'Erik', posts: [ 6 ] },
  { _id: 3, name: 'Alexa', posts: [] }
]

Example (sort) 200

GET /api/lists/authors?sort=name

[
  { _id: 3, name: 'Alexa', /* ... */ },
  { _id: 2, name: 'Erik', /* ... */ },
  { _id: 1, name: 'Ryan', /* ... */ }
]

Example (where) 200

GET /api/lists/authors?where={"posts":{"$ne":[]}}

[
  { _id: 1, name: 'Ryan', posts: [ 4, 5 ], /* ... */ },
  { _id: 2, name: 'Erik', posts: [ 6 ], /* ... */ }
]

Invalid where parameter 400

GET /api/lists/authors?where=123

{
  message: "The 'where' parameter is invalid.",
  link: 'https://www.jangle.io/errors/#where-parameter'
}

Example (status) 200

Note: This requires a user token, because it isn't published content!

GET /api/lists/authors?status=draft
Authorization: Bearer <good-token>
[
  { _id: 11, name: 'Rene', posts: [ 14, 15 ], /* ... */ },
  { _id: 12, name: 'Jackie', posts: [], /* ... */ }
]

Example (trash) 200

Note: This requires a user token, because it isn't published content!

GET /api/lists/authors?status=trash
Authorization: Bearer <good-token>
[
  { _id: 10, name: 'Rene', posts: [ 13 ] }
]

List API - Get

Gets item id in the name list.

GET /api/lists/:name/:id

Optional Parameters

Name Description Example
populate Related fields to expand (instead of returning the id). ?populate=author
select The fields you want to retrive ?select=50

Example 200

GET /api/lists/authors/1

{ _id: 1, name: 'Ryan', /* ... */ }

Example (populate) 200

GET /api/lists/authors/1?populate=posts

{ _id: 1,
  name: 'Ryan',
  posts: [
    { _id: 4, title: 'Introducing Jangle', /* ... */ },
    { _id: 5, title: 'Elm is Fun', /* ... */ }
  ],
  /* ... */
}

Example (select) 200

GET /api/lists/authors/1?select=name,posts

{ _id: 1, name: 'Ryan', posts: [ 4, 5 ] }

List API - Create

Creates an item in the name list.

POST /api/lists/:name

Example 200

POST /api/lists/authors
Authorization: Bearer <good-token>
Body: { "name": "Jimmy", posts: [] }
{
  _id: 7,
  name: "Jimmy",
  posts: [],
  jangle: {
    version: 1,
    created: { by: '<user>', at: '<time-1>' },
    updated: { by: '<user>', at: '<time-1>' }
  }
}

Missing required fields 400

POST /api/lists/authors
Authorization: Bearer <good-token>
Body: { "posts": [] }
{
  message: 'The item is missing required fields: name.',
  link: 'https://www.jangle.io/errors/#missing-required-fields'
}

List API - Update

Updates item id in the name list.

PUT /api/lists/:name/:id

Original Item

For the following examples, this is the item we're dealing with:

{
  _id: 7,
  name: "Jimmy",
  posts: [],
  jangle: {
    version: 1,
    created: { by: '<user>', at: '<time-1>' },
    updated: { by: '<user>', at: '<time-1>' }
  }
}

Example 200

PUT /api/lists/authors/7
Authorization: Bearer <good-token>
Body: { posts: [ 8 ] }



 

 

 



{
  _id: 7,
  name: "Jimmy",
  posts: [ 8 ],
  jangle: {
    version: 2,
    created: { by: '<user>', at: '<time-1>' },
    updated: { by: '<user>', at: '<time-2>' }
  }
}

Missing required fields 400

PUT /api/lists/authors/7
Authorization: Bearer <good-token>
Body: { "name": null }
{
  message: 'The item is missing required fields: name.',
  link: 'https://www.jangle.io/errors/#missing-required-fields'
}

List API - Remove

Removes item id in the name list.

DELETE /api/lists/:name/:id

Original Item

For the following examples, this is the item we're dealing with:

{
  _id: 7,
  name: "Jimmy",
  posts: [ 8 ],
  jangle: {
    version: 2,
    created: { by: '<user>', at: '<time-1>' },
    updated: { by: '<user>', at: '<time-2>' }
  }
}

Example 200

DELETE /api/lists/authors/7
Authorization: Bearer <good-token>



 

 

 



{
  _id: 7,
  name: "Jimmy",
  posts: [ 8 ],
  jangle: {
    version: 2,
    created: { by: '<user>', at: '<time-1>' },
    updated: { by: '<user>', at: '<time-2>' }
  }
}

List API - Publish

Publishes item id in the name list.

PUT /api/lists/:name/:id/publish

Original Item

For the following examples, this is the item we're dealing with:

{
  _id: 7,
  name: "Jimmy",
  posts: [ 8 ],
  jangle: {
    version: 2,
    created: { by: '<user>', at: '<time-1>' },
    updated: { by: '<user>', at: '<time-2>' }
  }
}

Example 200

PUT /api/lists/authors/7/publish
Authorization: Bearer <good-token>
{
  _id: 7,
  name: "Jimmy",
  posts: [ 8 ],
  jangle: {
    version: 2,
    created: { by: '<user>', at: '<time-1>' },
    updated: { by: '<user>', at: '<time-2>' }
  }
}

List API - Unpublish

Unpublishes item id in the name list.

PUT /api/lists/:name/:id/unpublish

Original Item

For the following examples, this is the item we're dealing with:

{
  _id: 7,
  name: "Jimmy",
  posts: [ 8 ],
  jangle: {
    version: 2,
    created: { by: '<user>', at: '<time-1>' },
    updated: { by: '<user>', at: '<time-2>' }
  }
}

Example 200

PUT /api/lists/authors/7/unpublish
Authorization: Bearer <good-token>
{
  _id: 7,
  name: "Jimmy",
  posts: [ 8 ],
  jangle: {
    version: 2,
    created: { by: '<user>', at: '<time-1>' },
    updated: { by: '<user>', at: '<time-2>' }
  }
}

List API - Preview

Previews an old version of item id in the name list.

GET /api/lists/:name/:id/preview/:version

Example 200

GET /api/lists/authors/7/preview/1
Authorization: Bearer <good-token>
{
  _id: 7,
  name: "Jimmy",
  posts: [],
  jangle: {
    version: 1,
    created: { by: '<user>', at: '<time-1>' },
    updated: { by: '<user>', at: '<time-1>' }
  }
}

Version not found 404

GET /api/lists/authors/7/preview/100
Authorization: Bearer <good-token>
{
  message: "Couldn't find that version of your item.",
  link: 'https://www.jangle.io/errors/#version-not-found'
}

List API - Restore

Restores an old version of item id in the name list.

PUT /api/lists/:name/:id/restore/:version

Example 200

PUT /api/lists/authors/7/restore/1
Authorization: Bearer <good-token>
{
  _id: 7,
  name: "Jimmy",
  posts: [],
  jangle: {
    version: 3,
    created: { by: '<user>', at: '<time-1>' },
    updated: { by: '<user>', at: '<time-3>' }
  }
}

Version not found 404

PUT /api/lists/authors/7/restore/100
Authorization: Bearer <good-token>
{
  message: "Couldn't find that version of your item.",
  link: 'https://www.jangle.io/errors/#version-not-found'
}

Item API

Overview

Name Route Description
Get GET /api/items/:name Gets the name item.
Update PUT /api/items/:name Updates the name item.
Publish PUT /api/items/:name/publish Publishes the name item.
Unpublish PUT /api/items/:name/unpublish Unpublishes the name item.
Preview GET /api/items/:name/preview Previews an old version of the name item.
Restore PUT /api/items/:name/restore Restores an old version of the name item.

Item API - Get

Gets the name item.

GET /api/items/:name

Optional Parameters

Name Description Example
populate Related fields to expand (instead of returning the id). ?populate=author
select The fields you want to retrive ?select=50

Example 200

GET /api/items/homepage

{ hero: { /* ... */ }, latest: { /* ... */ } }

Example (populate) 200

GET /api/items/homepage?populate=latest.posts

{ hero: { /* ... */ },
  latest: {
    title: 'Latest Posts',
    posts: [
      { _id: 4, title: 'Introducing Jangle', /* ... */ },
      { _id: 5, title: 'Elm is Fun', /* ... */ }
    ]
  }
}

Example (select) 200

GET /api/items/homepage?select=hero

{ hero: { /* ... */ } }

Item API - Update

Updates the name item.

PUT /api/items/:name

Original Item

For the following examples, this is the item we're dealing with:

{
  hero: {
    title: 'Jangle',
    description: 'A cms for humans'
  },
  latest: {
    title: 'Latest Posts',
    posts: [ 4, 5 ]
  },
  jangle: {
    version: 1,
    created: { by: '<user>', at: '<time-1>' },
    updated: { by: '<user>', at: '<time-1>' }
  }
}

Example 200

PUT /api/items/homepage
Authorization: Bearer <good-token>
Body: { latest: { posts: [ 8 ] } }



 

 

 








{
  hero: {
    title: 'Jangle',
    description: 'A cms for humans'
  },
  latest: {
    title: 'Latest Posts',
    posts: [ 8 ]
  },
  jangle: {
    version: 2,
    created: { by: '<user>', at: '<time-1>' },
    updated: { by: '<user>', at: '<time-2>' }
  }
}

Missing required fields 400

PUT /api/items/homepage
Authorization: Bearer <good-token>
Body: { "hero": null }
{
  message: 'The item is missing required fields: hero.',
  link: 'https://www.jangle.io/errors/#missing-required-fields'
}

Item API - Publish

Publishes the name item.

PUT /api/items/:name/publish

Original Item

For the following examples, this is the item we're dealing with:

{
  hero: {
    title: 'Jangle',
    description: 'A cms for humans'
  },
  latest: {
    title: 'Latest Posts',
    posts: [ 8 ]
  },
  jangle: {
    version: 2,
    created: { by: '<user>', at: '<time-1>' },
    updated: { by: '<user>', at: '<time-2>' }
  }
}

Example 200

PUT /api/items/homepage/publish
Authorization: Bearer <good-token>
{
  hero: {
    title: 'Jangle',
    description: 'A cms for humans'
  },
  latest: {
    title: 'Latest Posts',
    posts: [ 8 ]
  },
  jangle: {
    version: 2,
    created: { by: '<user>', at: '<time-1>' },
    updated: { by: '<user>', at: '<time-2>' }
  }
}

Item API - Unpublish

Unpublishes the name item.

PUT /api/items/:name/unpublish

Original Item

For the following examples, this is the item we're dealing with:

{
  hero: {
    title: 'Jangle',
    description: 'A cms for humans'
  },
  latest: {
    title: 'Latest Posts',
    posts: [ 8 ]
  },
  jangle: {
    version: 2,
    created: { by: '<user>', at: '<time-1>' },
    updated: { by: '<user>', at: '<time-2>' }
  }
}

Example 200

PUT /api/items/homepage/unpublish
Authorization: Bearer <good-token>
{
  hero: {
    title: 'Jangle',
    description: 'A cms for humans'
  },
  latest: {
    title: 'Latest Posts',
    posts: [ 8 ]
  },
  jangle: {
    version: 2,
    created: { by: '<user>', at: '<time-1>' },
    updated: { by: '<user>', at: '<time-2>' }
  }
}

Item API - Preview

Previews an old version of the name item.

GET /api/items/:name/preview/:version

Example 200

GET /api/items/homepage/preview/1
Authorization: Bearer <good-token>
{
  hero: {
    title: 'Jangle',
    description: 'A cms for humans'
  },
  latest: {
    title: 'Latest Posts',
    posts: [ 4, 5 ]
  },
  jangle: {
    version: 3,
    created: { by: '<user>', at: '<time-1>' },
    updated: { by: '<user>', at: '<time-3>' }
  }
}

Version not found 404

GET /api/items/homepage/preview/100
Authorization: Bearer <good-token>
{
  message: "Couldn't find that version of your item.",
  link: 'https://www.jangle.io/errors/#version-not-found'
}

Item API - Restore

Restores an old version of item id in the name list.

PUT /api/lists/:name/:id/restore/:version

Example 200

PUT /api/items/homepage/restore/1
Authorization: Bearer <good-token>
{
  hero: {
    title: 'Jangle',
    description: 'A cms for humans'
  },
  latest: {
    title: 'Latest Posts',
    posts: [ 4, 5 ]
  },
  jangle: {
    version: 3,
    created: { by: '<user>', at: '<time-1>' },
    updated: { by: '<user>', at: '<time-3>' }
  }
}

Version not found 404

PUT /api/items/homepage/restore/100
Authorization: Bearer <good-token>
{
  message: "Couldn't find that version of your item.",
  link: 'https://www.jangle.io/errors/#version-not-found'
}

Media API

Name Route Description
Find GET /api/media Finds items in the media library.
Get GET /api/media/:id Gets item id in the media library.
Create POST /api/media Creates an item in the media library.
Update PUT /api/media/:id Updates item id in the media library.
Remove DELETE /api/media/:id Permanently removes item id in the media library.

Schema API

Name Route Description
Find GET /api/schema Finds models.
Get GET /api/schema/:name Gets details for a model.

User API

Overview

Name Route Description
Sign In GET /api/users/sign-in Signs in a user, returning the user.
Find GET /api/users Finds a user.
Get GET /api/users/:id Gets user id.
Create POST /api/users Creates a new user.
Update PUT /api/users/:id Updates user id.
Remove DELETE /api/users/:id Permanently removes user id.

User API - Sign in

Signs in a user, returning that user.

GET /api/users/sign-in

Body

Name Description Example
email The email address for the user ryan@jangle.io
password The password for the user password

Example 200

GET /api/users/sign-in
Body: { "email": "ryan@jangle.io", "password": "password" }
{
  _id: 1,
  name: 'Ryan Haskell-Glatz',
  email: 'ryan@jangle.io',
  token: '1234567890'
}

Bad Password Example 400

GET /api/users/sign-in
Body: { "email": "ryan@jangle.io", "password": "something-bad" }
{
  message: `That email and password didn't work!`,
  link: 'https://www.jangle.io/errors/#bad-login'
}

Setup API

Name Route Description
Can Setup GET /api/can-setup Returns true if setup is allowed.
Setup PUT /api/setup Performs first-time setup (like signing up)

Stay tuned!

More documentation is on its way! 🎉