NAV
shell go

Introduction

Welcome to the Write.as API! Our API gives you full access to Write.as data and lets you build your own applications or utilities on top of it.

Our API is accessible at https://write.as/api/ (HTTPS only) and via our Tor hidden service at writeas7pm7rcdqg.onion/api/.

Backwards compatibility is important to us since we have a large set of clients in the wild. As we add new features, we usually add endpoints and properties alongside existing ones, but don’t remove or change finalized ones that are documented here. If a breaking API change is required in the future, we’ll version new endpoints and update these docs.

This documentation represents the officially-supported, latest API. Any properties or endpoints you discover in the API that aren’t documented here should be considered experimental or beta functionality, and subject to change without any notice.

Libraries

We have official libraries for various programming languages, and welcome any new ones created by the community.

Errors

Failed requests return with an error code and error_msg, like below.

{
  "code": "400",
  "error_msg": "Bad request."
}

The Write.as API uses conventional HTTP response codes to indicate the status of an API request. Codes in the 2xx range indicate success or that more information is available in the returned data, in the case of bulk actions. Codes in the 4xx range indicate that the request failed with the information provided. Codes in the 5xx range indicate an error with the Write.as servers (you shouldn’t see many of these).

Error Code Meaning
400 Bad Request – The request didn’t provide the correct parameters, or JSON / form data was improperly formatted.
401 Unauthorized – No valid user token was given.
403 Forbidden – You attempted an action that you’re not allowed to perform.
404 Not Found – The requested resource doesn’t exist.
405 Method Not Allowed – The attempted method isn’t supported.
410 Gone – The entity was unpublished, but may be back.
429 Too Many Requests – You’re making too many requests, especially to the same resource.
500, 502, 503 Server errors – Something went wrong on our end.

Responses

Successful requests return with a code in the 2xx range and a data object or array, depending on the request.

{
  "code": "200",
  "data": {
  }
}

All responses are returned in a JSON object containing two fields:

Field Type Description
code integer An HTTP status code in the 2xx range.
data object or array A single object for most requests; an array for bulk requests.

This wrapper will never contain an error_msg property at the top level.

Authentication

The API doesn’t require any authentication, either for the client or end user. However, if you want to perform actions on behalf of a user, you’ll need to pass a user access token with any requests:

Authorization: Token 00000000-0000-0000-0000-000000000000

See the Authenticate a User section for information on logging in.

Posts

Posts are the most basic entities on Write.as. Each can have its own appearance, and isn’t connected to any other Write.as entity by default. They can exist without an owner (user), or with an owner but no collection, or with both an owner and collection.

When posts are published with no owner, it’s up to the client to remember the posts a user has published. By keeping this information on the client, users can write anonymously – both according to Write.as and to anyone who reads any given post.

Users can choose between different appearances for each post, usually passed to and from the API as a font property:

Argument Appearance (Typeface) Word Wrap? Also on
sans Sans-serif (Open Sans) Yes N/A
serif / norm Serif (Lora) Yes N/A
wrap Monospace Yes N/A
mono Monospace No paste.as
code Syntax-highlighted monospace No paste.as

Publish a Post

c := NewClient()
p, err := c.CreatePost(&PostParams{
    Title:   "My First Post",
    Content: "This is a post.",
})
curl "https://write.as/api/posts" \
  -H "Content-Type: application/json" \
  -X POST \
  -d '{"body": "This is a post.", "title": "My First Post"}'

Example Response

{
  "code": 201,
  "data": {
    "id": "rf3t35fkax0aw",
    "slug": null,
    "token": "ozPEuJWYK8L1QsysBUcTUKy9za7yqQ4M",
    "appearance": "norm",
    "language": "",
    "rtl": false,
    "created": "2016-07-09T01:43:46Z",
    "title": "My First Post",
    "body": "This is a post.",
    "tags": [
    ]
  }
}

This creates a new post, associating it with a user account if authenticated. If the request is successful, the post will be available at:

Definition

POST https://write.as/api/posts

Arguments

Parameter Type Required Description
body string yes The content of the post.
title string no An optional title for the post. If none is given, Write.as will try to extract one for the browser’s title bar, but not change any appearance in the post itself.
font string no One of any post appearance types listed above. If invalid, defaults to serif.
lang string no ISO 639-1 language code, e.g. en or de.
rtl boolean no Whether or not the content should be display right-to-left, for example if written in Arabic or Hebrew.
crosspost array no Must be authenticated. An array of integrations and associated usernames to post to. See Crossposting.

Returns

The newly created post.

Retrieve a Post

c := writeas.NewClient()
p, err := c.GetPost("rf3t35fkax0aw")
curl https://write.as/api/posts/rf3t35fkax0aw

Example Response

{
  "code": 200,
  "data": {
    "id": "rf3t35fkax0aw",
    "slug": null,
    "appearance": "norm",
    "language": "",
    "rtl": false,
    "created": "2016-07-09T01:43:05Z",
    "title": "My First Post",
    "body": "This is a post.",
    "tags": [  
    ],
    "views": 0
  }
}

This retrieves a post entity. It includes extra Write.as data, such as page views and extracted hashtags.

Definition

GET https://write.as/api/posts/{POST_ID}

Update a Post

c := NewClient()
p, err := c.UpdatePost(&PostParams{
    ID:      "rf3t35fkax0aw",
    Token:   "ozPEuJWYK8L1QsysBUcTUKy9za7yqQ4M",
    Content: "My post is updated.",
})
curl "https://write.as/api/posts/rf3t35fkax0aw" \
  -H "Content-Type: application/json" \
  -X POST \
  -d '{"token": "ozPEuJWYK8L1QsysBUcTUKy9za7yqQ4M", "body": "My post is updated."}'

Example Response

{
  "code": 200,
  "data": {
    "id": "rf3t35fkax0aw",
    "slug": null,
    "appearance": "norm",
    "language": "",
    "rtl": false,
    "created": "2016-07-09T01:43:05Z",
    "title": "My First Post",
    "body": "This is an updated post.",
    "tags": [  
    ],
    "views": 0
  }
}

This updates an existing post.

Definition

POST https://write.as/api/posts/{POST_ID}

Arguments

Parameter Type Required Description
body string yes The content of the post.
title string no An optional title for the post. Supplying a parameter but leaving it blank will remove any title currently on the post.
font string no One of any post appearance types listed above. If invalid, it doesn’t change.
lang string no ISO 639-1 language code, e.g. en or de.
rtl boolean no Whether or not the content should be display right-to-left, for example if written in Arabic.

Returns

The entire post as it stands after the update.

Unpublish a Post

// Currently unsupported in the Go client.
// Use curl command or contribute at:
//   https://github.com/writeas/writeas-go
curl "https://write.as/api/posts/rf3t35fkax0aw" \
  -H "Content-Type: application/json" \
  -X POST \
  -d '{"token": "ozPEuJWYK8L1QsysBUcTUKy9za7yqQ4M", "body": ""}'

Response

{
  "code": 410,
  "error_msg": "Post unpublished by author."
}

This unpublishes an existing post. Simply pass an empty body to an existing post you’re authorized to update, and in the future that post will return a 410 Gone status.

The client will likely want to maintain what was in the body before the post was unpublished so it can be restored later.

Definition

POST https://write.as/api/posts/{POST_ID}

Arguments

Parameter Type Required Description
body string yes Should be an empty string ("").

Returns

An error 410 with a message: Post unpublished by author.

Delete a Post

c := NewClient()
err := c.DeletePost(&PostParams{
    ID:    "rf3t35fkax0aw",
    Token: "ozPEuJWYK8L1QsysBUcTUKy9za7yqQ4M",
})
curl "https://write.as/api/posts/rf3t35fkax0aw?token=ozPEuJWYK8L1QsysBUcTUKy9za7yqQ4M" \
  -X DELETE

A successful deletion returns a 204 with no content in the body.

This deletes a post.

Definition

DELETE https://write.as/api/posts/{POST_ID}

Arguments

Parameter Type Required Description
token string if unauth’d The post’s modify token.

Returns

A 204 status code and no content in the body.

Claim Posts

c := NewClient()
err := c.ClaimPosts(&[]OwnedPostParams{
    {
        ID:    "rf3t35fkax0aw",
        Token: "ozPEuJWYK8L1QsysBUcTUKy9za7yqQ4M",
    },
})
curl "https://write.as/api/posts/claim" \
  -H "Authorization: Token 00000000-0000-0000-0000-000000000000" \
  -X POST \
  -d '[{"id": "rf3t35fkax0aw", "token": "ozPEuJWYK8L1QsysBUcTUKy9za7yqQ4M"}]'

Always returns a 200

This adds unowned posts to a Write.as user / account.

Definition

POST https://write.as/api/posts/claim

Arguments

The body should contain an array of objects with these parameters:

Parameter Type Required Description
id string yes ID of the post to claim
token string yes Modify token of the post

Returns

A 200. Since this performs an action on multiple posts, the success/failure code are contained in each resulting post returned.

Collections

Collections are referred to as blogs on most of Write.as. Each gets its own shareable URL.

Each user has one collection matching their username, but can also have more collections connected to their account as a Pro user.

Create a Collection

// Currently unsupported in the Go client.
// Use curl command or contribute at:
//   https://github.com/writeas/writeas-go
curl "https://write.as/api/collections" \
  -H "Authorization: Token 00000000-0000-0000-0000-000000000000" \
  -X POST \
  -d '{"alias": "new-blog", "title": "The Best Blog Ever"}'

Example Response

{
  "code": 201,
  "data": {
    "alias": "new-blog",
    "title": "The Best Blog Ever",
    "description": "",
    "style_sheet": "",
    "email": "new-blog-wjn6epspzjqankz41mlfvz@writeas.com",
    "total_posts": 0
  }
}

This creates a new collection. Pro user required.

Definition

POST https://write.as/api/collections

Arguments

If only a title is given, the alias will be generated on the server from it. Clients should store the returned alias for future operations.

Parameter Type Required Description
title string yes An optional title for the post. If none is given, Write.as will try to extract one for the browser’s title bar, but not change any appearance in the post itself.
alias string no One of any post appearance types listed above. If invalid, defaults to serif.

Returns

The newly created collection.

Errors

Errors are returned with a user-friendly error message.

{
  "code": 403,
  "error_msg": "You must be a Pro user to do that."
}
Error Code Meaning
400 Request is missing required information, or has bad form data / JSON.
401 Incorrect information given.
403 You’re not a Pro user.
409 Alias is taken.
412 You’ve reached the maximum number of collections allowed.

Retrieve a Collection

// Currently unsupported in the Go client.
// Use curl command or contribute at:
//   https://github.com/writeas/writeas-go
curl https://write.as/api/collections/new-blog

Example Response

{
  "code": 200,
  "data": {
    "alias": "new-blog",
    "title": "The Best Blog Ever",
    "description": "",
    "style_sheet": "",
    "total_posts": 0
  }
}

This retrieves a collection and its metadata.

Definition

GET https://write.as/api/collections/{COLLECTION_ALIAS}

Returns

The requested collection.

Publish a Collection Post

// Currently unsupported in the Go client.
// Use curl command or contribute at:
//   https://github.com/writeas/writeas-go
curl "https://write.as/api/collections/new-blog/posts" \
  -H "Authorization: Token 00000000-0000-0000-0000-000000000000" \
  -H "Content-Type: application/json" \
  -X POST \
  -d '{"body": "This is a blog post.", "title": "My Post"}'

Example Response

{
  "code": 201,
  "data": {
    "id": "rf3t35fkax0aw",
    "slug": null,
    "token": "ozPEuJWYK8L1QsysBUcTUKy9za7yqQ4M",
    "appearance": "norm",
    "language": "",
    "rtl": false,
    "created": "2016-07-09T01:43:46Z",
    "title": "My First Post",
    "body": "This is a post.",
    "tags": [
    ]
  }
}

This creates a new post and adds it to the given collection. User must be authenticated.

Definition

POST https://write.as/api/collections/{COLLECTION_ALIAS}/posts

Arguments

This accepts the same arguments as anonymous posts, plus an optional crosspost parameter.

Parameter Type Required Description
body string yes The content of the post.
title string no An optional title for the post. If none is given, Write.as will try to extract one for the browser’s title bar, but not change any appearance in the post itself.
font string no One of any post appearance types listed above. If invalid, defaults to serif.
lang string no ISO 639-1 language code, e.g. en or de.
rtl boolean no Whether or not the content should be display right-to-left, for example if written in Arabic or Hebrew.
crosspost array no An array of integrations and associated usernames to post to. See Crossposting.

Returns

The newly created post.

Retrieve Collection Posts

// Currently unsupported in the Go client.
// Use curl command or contribute at:
//   https://github.com/writeas/writeas-go
curl https://write.as/api/collections/new-blog/posts

Example Response

{ 
  "code":200,
  "data": {  
    "alias": "new-blog",
    "title": "The Best Blog Ever",
    "description": "",
    "style_sheet": "",
    "private": false,
    "total_posts": 1,
    "posts": [
      {
        "id": "hjb7cvwaevy9eayp",
        "slug": "my-post",
        "appearance": "norm",
        "language": "",
        "rtl": false,
        "created": "2016-07-09T14:29:33Z",
        "title": "My Post",
        "body": "This is a blog post.",
        "tags": [
        ],
        "views": 0
      }
    ]
  }
}

This retrieves a collection’s posts along with the collection’s data.

Definition

GET https://write.as/api/collections/{COLLECTION_ALIAS}/posts

Returns

The requested collection and its posts in a posts array.

Move a Post to a Collection

// Currently unsupported in the Go client.
// Use curl command or contribute at:
//   https://github.com/writeas/writeas-go
curl "https://write.as/api/collections/new-blog/collect" \
  -H "Authorization: Token 00000000-0000-0000-0000-000000000000" \
  -H "Content-Type: application/json" \
  -X POST \
  -d '[{"id": "rf3t35fkax0aw", "token": "ozPEuJWYK8L1QsysBUcTUKy9za7yqQ4M"}]'

Example Response

{
  "code": 200,
  "data": [
    {
      "code": 200,
      "post": {
        "id": "rf3t35fkax0aw",
        "slug": "my-first-post",
        "token": "ozPEuJWYK8L1QsysBUcTUKy9za7yqQ4M",
        "appearance": "norm",
        "language": "",
        "rtl": false,
        "created": "2016-07-09T01:43:46Z",
        "title": "My First Post",
        "body": "This is a post.",
      }
    }
  ]
}

This adds a group of posts to a collection. This works for either posts that were created anonymously (i.e. don’t belong to the user account) or posts already owned by the user account.

Definition

POST https://write.as/api/collections/{COLLECTION_ALIAS}/collect

Arguments

Pass an array of objects, each containing the following parameters:

Parameter Type Required Description
id string yes The ID of the post to add to the collection
token string maybe* The post’s modify token. *Required if the post doesn’t belong to the requesting user.

Returns

A 200 at the top level for all requests. data contains an array of response envelopes: each with code and post (with full post data) on success, or code and error_msg on failure for any given post.

Users

Users have posts and collections associated with them that can be accessed across devices and browsers.

Having an account isn’t necessary to interact with Write.as, but provides useful functionality impossible or difficult to implement client-side. It does come with the trade-off of anonymity for pseudonymity.

However, Write.as is also set up to work well pseudo- and anonymously at the same time. If your client performs actions on behalf of a user, simply send the Authorization header for user actions and leave it off for anonymous requests, saving any published posts’ token like normal. Posts published anonymously can still be synced at any time, or never synced to ensure an account isn’t associated with posts a user doesn’t want to be associated with.

Authenticate a User

c := NewClient()
u, err := c.LogIn("matt", "12345")
curl "https://write.as/api/auth/login" \
  -H "Content-Type: application/json" \
  -X POST \
  -d '{"alias": "matt", "pass": "12345"}'

Example Response

{
  "code": 200,
  "data": {
    "access_token": "00000000-0000-0000-0000-000000000000",
    "user": {
      "username": "matt",
      "email": "matt@example.com",
      "created": "2015-02-03T02:41:19Z"
    }
  }
}

Authenticates a user with Write.as, creating an access token for future authenticated requests.

Users can only authenticate with their primary account, i.e. the first collection/blog they created, which may or may not have multiple collections associated with it.

Definition

POST https://write.as/api/auth/login

Arguments

Parameter Type Required Description
alias string yes The user’s username / alias.
pass string yes The user’s password.

Returns

A user access token and the authenticated user’s full data.

Errors

Errors are returned with a user-friendly error message.

{
  "code": 401,
  "error_msg": "Incorrect password."
}
Error Code Meaning
400 Request is missing required information, or has bad form data / JSON.
401 Incorrect information given.
404 User doesn’t exist.
429 You’re trying to log in too many times too quickly. You shouldn’t see this unless you’re doing something bad (in which case, please stop that).

Integrations

Write.as integrates with other services on the web to provide features like crossposting upon publishing a Write.as post.

On the web, these are known as channels, and if you’re logged in, they can be added here.

Crossposting

Example crosspost Parameter

"crosspost": [
  { "twitter": "writeas__" },
  { "twitter": "ilikebeans" },
  { "medium": "someone" },
  { "tumblr": "example" }
]

Crossposting is easy to do when publishing a new post. It requires authentication and at least one connected integration / channel on the web.

For the crosspost parameter on a new post, pass a JSON array of single-property objects. The object’s sole property should be the ID of the integration you want to crosspost to (see below), and its value should be the user’s connected username on that service.

ID Integration
twitter Twitter
tumblr Tumblr
medium Medium

Missing something? Questions? Find us on Slack, IRC, or other places and ask us.