Introduction
Welcome to API documentation for Write.as and WriteFreely! Our API gives you full read access and publishing abilities, enabling you to build your own full-featured applications or utilities.
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.
Write.as
Write.as is our hosted WriteFreely service.
It's also where we develop and test cutting-edge functionality that will eventually make its way into WriteFreely — sometimes many months after it's first available on our hosted platform. Due to this, there will be some differences between our hosted Write.as API and the API on a self-hosted WriteFreely instance. We indicate that in the documentation, where necessary.
Our API is accessible on the web at https://write.as/api/ and via our Tor onion service at writeasw4b635r4o3vec6mu45s47ohfyro5vayzx2zjwod4pjswyovyd.onion/api/.
WriteFreely
WriteFreely is self-hosted, open source software.
It powers our hosted platform, Write.as, plus hundreds of independent blogs and writing communities around the web.
Updates are pushed out via official releases at regular intervals, and as mentioned above, it may lag behind certain features in Write.as. Where necessary, we'll note differences between the two platforms' APIs in this documentation.
Developers
For developers building general-use libraries for our API, such as those found in the next section, we recommend that you build against features in the WriteFreely API only, so your library will be useful to the widest possible audience.
Branding and Use
In this case, we also recommend using the "WriteFreely" name instead of "Write.as" for your library — without, as always, implying that your library is officially developed and supported by the Write.as team.
Libraries
These are our official API libraries:
Language | Docs | Repository | WriteFreely Support? |
---|---|---|---|
Go | GoDoc | GitHub | Yes |
Swift | SwiftDoc | GitHub | Yes |
Java | GitHub | No |
These libraries were created by the community — let us know if you create one, too!
Language | Docs | Repository | WriteFreely Support? |
---|---|---|---|
Vala | README | GitHub | Yes |
PHP | README | GitHub | Yes |
.NET Core | README | GitHub | Yes |
Python | README | GitHub | No |
Javascript | npm | GitHub | No |
Getting Started
Here are some basic concepts to understand before getting started with the WriteFreely / Write.as API.
Design
Our API tries to follow standard RESTful patterns.
Backwards compatibility is important to us since we have a large set of Write.as apps and WriteFreely apps 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.
Terminology
This is the internal terminology we use for certain concepts in WriteFreely. We recommend adopting it for similar concepts in your apps and libraries.
- Post: A Markdown-based article, with metadata.
- Collection: Known as a "blog" to users, though that's not necessarily its only use. It holds one or more Posts.
- User: A registered account representing an individual, with a password, email, and access to Posts and Collections.
Errors
Failed requests return with an error
code
anderror_msg
, like below.
{
"code": "400",
"error_msg": "Bad request."
}
The WriteFreely 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 WriteFreely server (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 the2xx
range and adata
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.
Formatting
The bulk of formatting on WriteFreely is done with Markdown, a plain-text formatting syntax that converts to HTML.
Render Markdown
curl "https://write.as/api/markdown" \
-H "Content-Type: application/json" \
-X POST \
-d '{"raw_body": "This is *formatted* in __Markdown__."}'
Example Response
{
"code": 200,
"data": {
"body": "<p>This is the <em>formated</em> in <strong>Markdown</strong>.</p> ",
}
}
This takes raw Markdown and renders it into usable HTML.
Definition
POST /api/markdown
Arguments
Parameter | Type | Required | Description |
---|---|---|---|
raw_body | string | yes | The raw Markdown string. |
collection_url | string | no | If given, hashtags in the supplied Markdown will automatically be linked to the correct page on the given collection (blog). |
Returns
A 200
at the top level for all valid, authenticated requests. data
contains body, the rendered HTML from the given Markdown.
Errors
Errors are returned with a user-friendly error message.
{
"code": 400,
"error_msg": "Expected valid JSON object."
}
Error Code | Meaning |
---|---|
400 | JSON parsing failed. |
Posts
Posts are the most basic entities on WriteFreely. Each can have its own appearance, and isn't connected to any other WriteFreely entity by default.
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 := writeas.NewClient()
p, err := c.CreatePost(&writeas.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"}'
let client = WFClient(for: "https://write.as")
let post = WFPost(body: "This is a post.", title: "My First Post")
client.createPost(post: post) { result in
switch result {
case .success(let post):
// Do something with the returned WFPost
case .failure(let error):
// Do something with the returned WFError
}
}
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 all of these locations:
- write.as/
{id}
- writeasw4b635r4o3vec6mu45s47ohfyro5vayzx2zjwod4pjswyovyd.onion/
{id}
- paste.as/
{id}
-- iffont
was code or mono
Authentication
This can be done anonymously or while authenticated.
Definition
POST /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, WriteFreely 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. |
created | date | no | An optional published date for the post, formatted 2006-01-02T15:04:05Z . |
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
let client = WFClient(for: "https://write.as")
client.getPost(byID: "rf3t35fkax0aw") { result in
switch result {
case .success(let post):
// Do something with the returned WFPost
case .failure(let error):
// Do something with the returned WFError
}
}
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 WriteFreely data, such as page views and extracted hashtags.
Definition
GET /api/posts/{POST_ID}
Update a Post
c := writeas.NewClient()
p, err := c.UpdatePost("rf3t35fkax0aw", "ozPEuJWYK8L1QsysBUcTUKy9za7yqQ4M", &writeas.PostParams{
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."}'
let client = WFClient(for "https://write.as")
let updatedPost = WFPost(body: "My post is updated.")
client.updatePost(postId: "", updatedPost: updatedPost) { result in
switch result {
case .success(let post):
// Do something with the returned WFPost
case .failure(let error):
// Do something with the returned WFError
}
}
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": "My post is updated.",
"tags": [
],
"views": 0
}
}
This updates an existing post.
Authentication
This can be done anonymously or while authenticated.
Definition
POST /api/posts/{POST_ID}
Arguments
Parameter | Type | Required | Description |
---|---|---|---|
token | string | maybe* | The post's modify token. *Required if not authenticated. |
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": ""}'
// Not yet implemented in the Swift package. Contribute at:
// https://github.com/writefreely/writefreely-swift
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 /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 := writeas.NewClient()
err := c.DeletePost("rf3t35fkax0aw", "ozPEuJWYK8L1QsysBUcTUKy9za7yqQ4M")
curl "https://write.as/api/posts/rf3t35fkax0aw?token=ozPEuJWYK8L1QsysBUcTUKy9za7yqQ4M" \
-X DELETE
let client = WFClient(for: "https://write.as")
// Deleting a post that doesn't belong to the requesting user is currently unsupported.
client.deletePost(postId: "rf3t35fkax0aw") { result in
switch result {
case .success():
// Notify the user that the post was deleted successfully
case .failure(let error):
// Do something with the returned WFError
}
}
A successful deletion returns a
204
with no content in the body.
This deletes a post.
Definition
DELETE /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 := writeas.NewClient()
c.SetToken("00000000-0000-0000-0000-000000000000")
err := c.ClaimPosts(&[]OwnedPostParams{
{
ID: "rf3t35fkax0aw",
Token: "ozPEuJWYK8L1QsysBUcTUKy9za7yqQ4M",
},
})
curl "https://write.as/api/posts/claim" \
-H "Authorization: Token 00000000-0000-0000-0000-000000000000" \
-H "Content-Type: application/json" \
-X POST \
-d '[{"id": "rf3t35fkax0aw", "token": "ozPEuJWYK8L1QsysBUcTUKy9za7yqQ4M"}]'
// Not yet implemented in the Swift package. Contribute at:
// https://github.com/writefreely/writefreely-swift
Always returns a
200
This adds unowned posts to a WriteFreely user / account.
Definition
POST /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 WriteFreely. 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 Casual or Pro user.
Create a Collection
c := writeas.NewClient()
c.SetToken("00000000-0000-0000-0000-000000000000")
coll, err := c.CreateCollection(&CollectionParams{
Alias: "new-blog",
Title: "The Best Blog Ever",
})
curl "https://write.as/api/collections" \
-H "Authorization: Token 00000000-0000-0000-0000-000000000000" \
-H "Content-Type: application/json" \
-X POST \
-d '{"alias": "new-blog", "title": "The Best Blog Ever"}'
let client = WFClient(for: "https://write.as")
client.createCollection(token: "00000000-0000-0000-0000-000000000000", withTitle: "The Best Blog Ever", alias: "new-blog") { result in
switch result {
case .success(let collection):
// Do something with the returned WFCollection
case .failure(let error):
// Do something with the returned WFError
}
}
Example Response
{
"code": 201,
"data": {
"alias": "new-blog",
"title": "The Best Blog Ever",
"description": "",
"style_sheet": "",
"email": "new-blog-wjn6epspzjqankz41mlfvz@writeas.com",
"views": 0,
"total_posts": 0
}
}
This creates a new collection. Casual or Pro user required.
Definition
POST /api/collections
Arguments
Clients must supply either a title
or alias
(or both). If only a title
is given, the alias
will be generated from it.
Parameter | Type | Required | Description |
---|---|---|---|
alias | string | no | A valid collection slug / alias, containing only alphanumerics and hyphens. If none given, WriteFreely will generate a valid alias from the title value. |
title | string | no | An optional title for the collection. If none given, WriteFreely will use the alias value. |
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
c := writeas.NewClient()
coll, err := c.GetCollection("new-blog")
curl https://write.as/api/collections/new-blog
let client = WFClient(for: "https://write.as")
client.getCollection(withAlias: "new-blog") { result in
switch result {
case .success(let collection):
// Do something with the returned WFCollection
case .failure(let error):
// Do something with the returned WFError
}
}
Example Response
{
"code": 200,
"data": {
"alias": "new-blog",
"title": "The Best Blog Ever",
"description": "",
"style_sheet": "",
"public": true,
"views": 9,
"total_posts": 0
}
}
This retrieves a collection and its metadata.
Authentication
Collections can be retrieved without authentication. However, authentication is required for retrieving a private collection or one with scheduled posts.
Definition
GET /api/collections/{COLLECTION_ALIAS}
Returns
The requested collection.
Update 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" \
-H "Authorization: Token 00000000-0000-0000-0000-000000000000" \
-H "Content-Type: application/json" \
-X POST \
-d '{"description": "A great blog.", "style_sheet": "body { color: blue; }"}'
// Not yet implemented in the Swift package. Contribute at:
// https://github.com/writefreely/writefreely-swift
Example Response
{
"code": 200,
"data": {
"alias": "new-blog",
"title": "The Best Blog Ever",
"description": "A great blog.",
"style_sheet": "body { color: blue; }",
"script": "",
"email": "new-blog-wjn6epspzjqankz41mlfvz@writeas.com",
"views": 0
}
}
This updates attributes of an existing collection.
Definition
POST /api/collections/{COLLECTION_ALIAS}
Arguments
Supply only the fields you would like to update. Any fields left out will remain unchanged.
Parameter | Type | Required | Description |
---|---|---|---|
title | string | no | The title of the collection. |
description | string | no | The collection description. |
style_sheet | string | no | The full custom CSS to apply to the collection. WriteFreely or Write.as Pro |
script | string | no | The full custom Javascript to apply to the collection. Supported only on Write.as |
visibility | integer | no | The collection Publicity setting. Must be one of the valid options listed below. WriteFreely or Write.as Pro |
pass | string | no | The password required to view a password-protected blog (visibility = 4 ). |
mathjax | boolean | no | Whether or not to enable MathJax rendering on the collection. |
Valid visibility
values
Valid options for the visibility
field:
0
= Unlisted1
= Public2
= Private4
= Password-protected; requirespass
value
Returns
The newly created collection.
Errors
This endpoint will attempt to update all possible fields. It will silently skip values it cannot update, such as premium attributes on Write.as, or the Public visibility option when a WriteFreely instance isn't configured for this.
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. |
Delete 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 \
-H "Authorization: Token 00000000-0000-0000-0000-000000000000" \
-H "Content-Type: application/json" \
-X DELETE
let client = WFClient(for: "https://write.as")
client.deleteCollection(token: "00000000-0000-0000-0000-000000000000", withAlias: "new-blog") { result in
switch result {
case .success():
// Notify the user that the collection was deleted successfully
case .failure(let error):
// Do something with the returned WFError
}
}
A successful deletion returns a
204
with no content in the body.
This permanently deletes a collection and makes any posts on it anonymous.
Definition
DELETE /api/collections/{COLLECTION_ALIAS}
Returns
A 204
status code and no content in the body.
Retrieve 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/my-first-post
let client = WFClient(for: "https://write.as")
client.getPost(bySlug: "my-first-post", from: "new-blog") { result in
switch result {
case .success(let post):
// Do something with the returned WFPost
case .failure(let error):
// Do something with the returned WFError
}
}
Example Response
{
"code": 200,
"data": {
"id": "hjb7cvwaevy9eayp",
"slug": "my-first-post",
"appearance": "norm",
"language": "",
"rtl": false,
"created": "2016-07-09T14:29:33Z",
"title": "My First Post",
"body": "This is a blog post.",
"tags": [
],
"views": 0
}
}
This retrieves a single post from a collection by the post's slug.
Authentication
Collection posts can be retrieved without authentication. However, authentication is required for retrieving a post from a private collection.
Definition
GET /api/collections/{COLLECTION_ALIAS}/posts/{SLUG}
Returns
The requested collection post.
Publish a Collection Post
c := writeas.NewClient()
c.SetToken("00000000-0000-0000-0000-000000000000")
p, err := c.CreatePost(&writeas.PostParams{
Title: "My First Post",
Content: "This is a post.",
Collection: "new-blog",
})
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 First Post"}'
let client = WFClient(for: "https://write.as")
let post = WFPost(token: "00000000-0000-0000-0000-000000000000", body: "This is a post.", title: "My First Post")
client.createPost(post: post, in: "new-blog") { result in
switch result {
case .success(let post):
// Do something with the returned WFPost
case .failure(let error):
// Do something with the returned WFError
}
}
Example Response
{
"code": 201,
"data": {
"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.",
"tags": [
],
"collection": {
"alias": "new-blog",
"title": "The Best Blog Ever",
"description": "",
"style_sheet": "",
"public": true,
"url": "https://write.as/new-blog/"
}
}
}
This creates a new post and adds it to the given collection. User must be authenticated.
Definition
POST /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 given, WriteFreely will try to extract one for the browser's title bar, but not change any appearance in the post itself. |
slug | string | no | An optional slug for the post. If none given, WriteFreely will automatically generate one from the given title, or from the post body if no title given. |
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. |
created | date | no | An optional published date for the post, formatted 2006-01-02T15:04:05Z . |
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
let client = WFClient(for: "https://write.as")
client.getPosts(in: "new-blog") { result in
switch result {
case .success(let posts):
// Do something with the returned WFPost array
case .failure(let error):
// Do something with the returned WFError
}
}
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-first-post",
"appearance": "norm",
"language": "",
"rtl": false,
"created": "2016-07-09T14:29:33Z",
"title": "My First Post",
"body": "This is a blog post.",
"tags": [
],
"views": 0
}
]
}
}
This retrieves a collection's posts along with the collection's data.
Authentication
Collection posts can be retrieved without authentication. However, authentication is required for retrieving a private collection or one with scheduled posts.
Definition
GET /api/collections/{COLLECTION_ALIAS}/posts
Arguments
Query string parameters can be passed to affect post presentation.
Parameter | Type | Required | Description |
---|---|---|---|
body | string | no | Desired post body format. Can be left blank for raw text, or html to get back HTML generated from Markdown. |
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"}]'
let client = WFClient(for: "https://write.as")
client.movePost(token: "00000000-0000-0000-0000-000000000000", postId: "rf3t35fkax0aw", to: "new-blog") { result in
switch result {
case .success():
// Notify the user that the post was moved successfully
case .failure(let error):
// Do something with the returned WFError
}
}
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 /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 valid, authenticated 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.
Pin a Post to a Collection
c := writeas.NewClient()
c.SetToken("00000000-0000-0000-0000-000000000000")
err := c.PinPost(&writeas.PinnedPostParams{
ID: "rf3t35fkax0aw",
Position: 1,
})
curl "https://write.as/api/collections/new-blog/pin" \
-H "Authorization: Token 00000000-0000-0000-0000-000000000000" \
-H "Content-Type: application/json" \
-X POST \
-d '[{"id": "rf3t35fkax0aw", "position": 1}]'
let client = WFClient(for: "https://write.as")
client.pinPost(token: "00000000-0000-0000-0000-000000000000", postId: "rf3t35fkax0aw", at: 1, in: "new-blog") { result in
switch result {
case .success():
// Notify the user that the post was pinned successfully
case .failure(let error):
// Do something with the returned WFError
}
}
Example Response
{
"code": 200,
"data": [
{
"id": "rf3t35fkax0aw",
"code": 200
}
]
}
This pins a blog post to a collection. It'll show up as a navigation item in the collection/blog home page header, instead of on the blog itself.
Definition
POST /api/collections/{COLLECTION_ALIAS}/pin
Arguments
Pass an array of objects, each containing the following parameters:
Parameter | Type | Required | Description |
---|---|---|---|
id | string | yes | The ID of the post to pin to the collection |
position | integer | no | The numeric position in which to pin the post. Will pin at end of list if no parameter is given. |
Returns
A 200
at the top level for all valid, authenticated requests. data
contains an array of response envelopes: each with code
and id
, plus error_msg
on failure for any given post.
Unpin a Post from a Collection
c := writeas.NewClient()
c.SetToken("00000000-0000-0000-0000-000000000000")
err := c.UnpinPost(&writeas.PinnedPostParams{
ID: "rf3t35fkax0aw",
})
curl "https://write.as/api/collections/new-blog/unpin" \
-H "Authorization: Token 00000000-0000-0000-0000-000000000000" \
-H "Content-Type: application/json" \
-X POST \
-d '[{"id": "rf3t35fkax0aw"}]'
let client = WFClient(for: "https://write.as")
client.unpinPost(token: "00000000-0000-0000-0000-000000000000", postId: "rf3t35fkax0aw", from: "new-blog") { result in
switch result {
case .success():
// Notify the user that the post was unpinned successfully
case .failure(let error):
// Do something with the returned WFError
}
}
Example Response
{
"code": 200,
"data": [
{
"id": "rf3t35fkax0aw",
"code": 200
}
]
}
This unpins a blog post from a collection. It'll remove the navigation item from the collection/blog home page header and put the post back on the blog itself.
Definition
POST /api/collections/{COLLECTION_ALIAS}/unpin
Arguments
Pass an array of objects, each containing the following parameters:
Parameter | Type | Required | Description |
---|---|---|---|
id | string | yes | The ID of the post to unpin from the collection |
Returns
A 200
at the top level for all valid, authenticated requests. data
contains an array of response envelopes: each with code
and id
, plus 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.
WriteFreely use always requires a registered account.
Write.as doesn't require an account to interact with it. Here, accounts provide 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 := writeas.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"}'
let client = WFClient(for: "https://write.as")
client.login(username: "matt", password: "12345") { result in
switch result {
case .success():
// Do something with the returned WFUser
case .failure(let error):
// Do something with the returned WFError
}
}
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 WriteFreely, 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 /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). |
Log User Out
c := writeas.NewClient()
c.SetToken("00000000-0000-0000-0000-000000000000")
err := c.LogOut()
curl "https://write.as/api/auth/me" \
-H "Authorization: Token 00000000-0000-0000-0000-000000000000" \
-X DELETE
let client = WFClient(for: "https://write.as")
client.logout(token: "00000000-0000-0000-0000-000000000000") { result in
switch result {
case .success():
// Notify the user that they were logged out successfully
case .failure(let error):
// Do something with the returned WFError
}
}
A successful deletion returns a
204
with no content in the body.
Un-authenticates a user with WriteFreely, permanently invalidating the access token used with the request.
Definition
DELETE /api/auth/me
Returns
A 204
status code and no content in the body.
Errors
Errors are returned with a user-friendly error message.
{
"code": 404,
"error_msg": "Token is invalid."
}
Error Code | Meaning |
---|---|
400 | Request is missing an access token / Authorization header |
404 | Token is invalid. |
Retrieve Authenticated User
// Currently unsupported in the Go client.
// Use curl command or contribute at:
// https://github.com/writeas/writeas-go
curl "https://write.as/api/me" \
-H "Authorization: Token 00000000-0000-0000-0000-000000000000" \
-H "Content-Type: application/json" \
-X GET
let client = WFClient(for: "https://write.as")
client.getUserData(token: "00000000-0000-0000-0000-000000000000") { result in
switch result {
case .success():
// Do something with the returned WFUser
case .failure(let error):
// Do something with the returned WFError
}
}
Example Response
{
"code": 200,
"data": {
"username": "matt"
}
}
Definition
GET /api/me
Arguments
None.
Returns
An authenticated user's basic data.
Errors
Errors are returned with a user-friendly error message.
{
"code": 401,
"error_msg": "Invalid access token."
}
Error Code | Meaning |
---|---|
401 | Access token is invalid or expired. |
Retrieve User's Posts
c := writeas.NewClient()
c.SetToken("00000000-0000-0000-0000-000000000000")
p, err := c.GetUserPosts()
curl "https://write.as/api/me/posts" \
-H "Authorization: Token 00000000-0000-0000-0000-000000000000" \
-H "Content-Type: application/json" \
-X GET
let client = WFClient(for: "https://write.as")
client.getPosts(token: "00000000-0000-0000-0000-000000000000") { result in
switch result {
case .success(let posts):
// Do something with the returned WFPost array
case .failure(let error):
// Do something with the returned WFError
}
}
Example Response
{
"code": 200,
"data": [
{
"id": "7xe2dbojynjs1dkk",
"slug": "cool-post",
"appearance": "norm",
"language": "en",
"rtl": false,
"created": "2017-11-12T03:49:36Z",
"updated": "2017-11-12T03:49:36Z",
"title": "",
"body": "Cool post!",
"tags": [],
"views": 0,
"collection": {
"alias": "matt",
"title": "Matt",
"description": "My great blog!",
"style_sheet": "",
"public": true,
"views": 46
}
},
{
"id": "rf3t35fkax0aw",
"slug": null,
"appearance": "norm",
"language": "",
"rtl": false,
"created": "2016-07-09T01:43:46Z",
"updated": "2016-07-09T01:43:46Z",
"title": "My First Post",
"body": "This is a post.",
"tags": [],
"views": 0
}
]
}
Definition
GET /api/me/posts
Arguments
None.
Returns
An array of the authenticated user's posts, including anonymous and collection posts.
Errors
Errors are returned with a user-friendly error message.
{
"code": 401,
"error_msg": "Invalid access token."
}
Error Code | Meaning |
---|---|
401 | Access token is invalid or expired. |
Retrieve User's Collections
c := writeas.NewClient()
c.SetToken("00000000-0000-0000-0000-000000000000")
colls, err := c.GetUserCollections()
curl "https://write.as/api/me/collections" \
-H "Authorization: Token 00000000-0000-0000-0000-000000000000" \
-H "Content-Type: application/json" \
-X GET
let client = WFClient(for: "https://write.as")
client.getUserCollections(token: "00000000-0000-0000-0000-000000000000") { result in
switch result {
case .success(let collections):
// Do something with the returned WFCollection array
case .failure(let error):
// Do something with the returned WFError
}
}
Example Response
{
"code": 200,
"data": [
{
"alias": "matt",
"title": "Matt",
"description": "My great blog!",
"style_sheet": "",
"public": true,
"views": 46,
"email": "matt-7e7euebput9t5jr3v4csgferutf@writeas.com",
"url": "https://write.as/matt/"
}
]
}
Definition
GET /api/me/collections
Arguments
None.
Returns
All of an authenticated user's owned collections.
Errors
Errors are returned with a user-friendly error message.
{
"code": 401,
"error_msg": "Invalid access token."
}
Error Code | Meaning |
---|---|
401 | Access token is invalid or expired. |
Retrieve User's Channels
// Currently unsupported in the Go client.
// Use curl command or contribute at:
// https://github.com/writeas/writeas-go
curl "https://write.as/api/me/channels" \
-H "Authorization: Token 00000000-0000-0000-0000-000000000000" \
-H "Content-Type: application/json" \
-X GET
// Not yet implemented in the Swift package. Contribute at:
// https://github.com/writefreely/writefreely-swift
Example Response
{
"code": 200,
"data": [
{
"id":"twitter",
"name":"Twitter",
"username":"ilikebeans"
},
{
"id":"mastodon",
"url":"writing.exchange",
"name":"writing.exchange",
"username":"matt"
}
]
}
Definition
GET /api/me/channels
Arguments
None.
Returns
An array of the authenticated user's connected channels, or integrations.
For channels that aren't a centralized service, like Mastodon, you'll also see a url
property of the specific instance or host that the user has connected to.
Errors
Errors are returned with a user-friendly error message.
{
"code": 401,
"error_msg": "Invalid access token."
}
Error Code | Meaning |
---|---|
401 | Access token is invalid or expired. |
Integrations
Supported only on Write.as
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" }
]
Supported only on Write.as
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 |
---|---|
tumblr | Tumblr |
medium | Medium |
Missing something? Questions? Find us on Slack, IRC, or other places and ask us.