Introduction
Heads up! Most of the Ruby examples use the
scripted_client
RubyGem. To get started using the Gem, check out the docs!
Welcome to the Scripted API. As far as we know, it’s the only way to programatically and scaleably generate original, creative written content.
This API follows RESTful conventions and communicates in JSON. One caveat before we begin: due to the nature of content, this API is kind of asynchronous. You can post a job synchronously, but you’ll have to come back 3-5 days later to retrieve the written content. If you need us to fire webhooks upon events (e.g. - “your content is ready for review”) please get in touch!
Authentication
Don’t forget to replace
abcd1234
with your Organization Key andabcdefghij0123456789
with your Token!
require 'net/http'
require 'json'
uri = URI.parse("https://api.scripted.com/abcd1234/v1/industries")
Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
request = Net::HTTP::Get.new(uri)
request["Content-Type"] = "application/json"
request["Authorization"] = "Bearer abcdefghij0123456789"
response = http.request request
parsed = JSON.parse(response.body)
end
curl -H "Authorization: Bearer abcdefghij0123456789" \
https://api.scripted.com/abcd1234/v1/industries
We use two factors to authenticate requests, an Organization Key and a JSON Web Token. You can find both of them in your Scripted dashboard by navigating to Account Settings and clicking on the API tab. All requests are namespaced under the relevant Organization Key, for example:
GET /abcd1234/v1/jobs
Tokens are passed as a Bearer
in the Authorization
header, like so:
Bearer abcdefghij0123456789
All requests must be made over HTTPS
.
Errors
Errors are returned in an Array like so:
{
"errors": [
"Topic is too long (maximum is 255 characters)",
"Quantity must be one of the ContentFormat's quantity_options"
]
}
The Scripted API does not always return a 200 OK
status.
Status | Meaning |
---|---|
400 | Bad Request – The request was malformed in some way. |
401 | Unauthorized – The Token provided was invalid or expired. |
403 | Forbidden – The Token provided does not have access to the namespace’s Organization Key. |
404 | Not Found – The specified resource could not be found |
500 | Internal Server Error – We had a problem with our server. Try again later. |
503 | Service Unavailable – We’re temporarially offline for maintanance. Please try again later. |
Jobs
Sample Job
{
"id": "5654ec06a6e02a37e7000318",
"topic": "Where to buy an Orangutan",
"state": "copyediting",
"quantity": 1,
"delivery": "standard",
"deadline_at": "2015-12-04T01:30:00Z",
"created_at": "2015-11-24T23:00:22Z",
"pitch": null,
"content_format": {
"id": "5654ec02a6e02a37e70000d5",
"name": "Standard Blog Post",
"pitchable": true,
"length_metric": "350-450 words",
"quantity_options": [
1
]
},
"pricing": {
"total": 9900
},
"writer": {
"id": "5654ec01a6e02a37e700003b",
"nickname": "Bob L.",
"favorite": true
},
"document": {
"id": "5654ec06a6e02a37e700031a",
"type": "Document"
},
"prompts": [
{
"id": "5654ec06a6e02a37e700031e",
"kind": "checkbox",
"label": "Goal",
"description": "Select one or many",
"value": [
"Informed analysis"
],
"answer_required": false,
"value_options": [
"Informed analysis",
"Thought leadership",
"Repurpose existing writing",
"Promote topic",
]
}
],
"guidelines": [
{
"id": "5654ebfc93be3b2c623b6e63",
"name": "Anecdotal",
"kind": "Tone"
}
],
"industries": [
{
"id": "5654ebfc93be3b2c623b6e2a",
"name": "Education"
}
]
}
Jobs are at the very center of the Scripted domain model. They represent a request for writing to be fulfilled by one of our freelancers. They are forged from a JobTemplate, and include either a list of Industries or a single Specialty. Specialist jobs are completed by freelancers with more depth of knowledge, so they cost a bit more.
state
can be any of the following:
State | Meaning |
---|---|
hold | Paused by a Scripted employee for further clarification. |
awaiting author | Submitted to Writers but not yet claimed. |
writing | In the process of being written. |
copyediting | Being screened for plagiarism or reviewed by an Editor. |
ready for review | First draft is ready and awaiting your revision requests. |
revising | Writer is incorporating your revision requests. |
ready for acceptance | Final draft is ready. You can either accept or reject. |
accepted | Accepted and charged. |
canceled | Canceled and not charged. |
rejected | Rejected and not charged. |
Create a Job
require 'scripted_client'
# First, find a JobTemplate that you'd like to use:
templates = ScriptedClient::JobTemplate.all
blog_post = templates.find { |template| template.name == 'Standard Blog Post' }
# Next, assign some values for the Prompts on that JobTemplate.
key_points = blog_post.prompts.find { |prompt| prompt.label == 'Key Points' }
key_points.value = ['Orangutans make great pets', 'Normal pets are lame']
# Next, you can find an Industry:
industries = ScriptedClient::Industry.all
lifestyle = industries.find { |industry| industry.name == 'Lifestyle & Travel' }
# Now you can create the Job!
job = ScriptedClient::Job.new(
topic: 'Top 10 Reasons to Buy an Orangutan',
job_template: blog_post,
industries: [lifestyle]
)
job.save
# => true
curl -H "Authorization: Bearer abcdefghij0123456789" \
https://api.scripted.com/abcd1234/v1/jobs \
-d topic="Why a rug can tie a room together" \
-d job_template[id]=5ceb8bb8235bcc76bf475e21 \
-d job_template[prompts][][id]=52cdda2f8b588f7e02000062 \
-d job_template[prompts][][value]=Yes \
-d job_template[prompts][][id]=52cdda2f8b588f7e02000064 \
-d job_template[prompts][][value]=#apifuntimes \
-d guidelines[][id]=e9d378cc8e74b4b5faa34892935d5665 \
-d guidelines[][id]=689ca0246221f2f21e80e9dac9387bce \
-d industries[][id]=8af7c7ae67b6d0854051378b9f3eede4 \
-d industries[][id]=92e4132ea0560de3c12f072dc9a81486 \
-d industries[][id]=f0709aabc7050b3eed7c43db83f0f4cf
POST /:organization_key/v1/jobs
To create a Job, pass a JobTemplate and answers for that JobTemplate’s Prompts. Either a list of Industries or a single Specialty must also be included. Guidelines are optional.
Parameter | Description |
---|---|
job_template |
Required Pick a JobTemplate with a ContentFormat whose pitchable is true. |
topic |
Required What’s this Job all about? |
quantity |
Optional How many do you want? Defaults to minimum of the ContentFormat#quantity_options . |
industries |
Required unless Specialty is passed A list of high-level categories pertaining to your job. |
specialty |
Required unless Industries are passed A deeper field of knowledge. |
guidelines |
Optional Tone, Voice and Perspective. |
List all Jobs
ScriptedClient::Job.all
# Or if you want to filter by state
ScriptedClient::Job.draft_ready
curl -H "Authorization: Bearer abcdefghij0123456789" \
https://api.scripted.com/abcd1234/v1/jobs
# Or if you want to filter by state
curl -H "Authorization: Bearer abcdefghij0123456789" \
https://api.scripted.com/abcd1234/v1/jobs/draft_ready
GET /:organization_key/v1/jobs
You can pass any of the following filters to narrow your results:
screening
writing
draft_ready
revising
final_ready
in_progress
needs_review
accepted
rejected
finished
See Pagination if you have more than 15 jobs.
Show a Job
Don’t forget to replace
5ceb8bb8235bcc76bf475e21
with theID
of one of your jobs!
ScriptedClient::Job.find('5ceb8bb8235bcc76bf475e21')
curl -H "Authorization: Bearer abcdefghij0123456789" \
https://api.scripted.com/abcd1234/v1/jobs/5ceb8bb8235bcc76bf475e21
GET /:organization_key/v1/jobs/:id
Get a Job’s HTML Contents
ScriptedClient::Job.find('5ceb8bb8235bcc76bf475e21').html_contents
curl -H "Authorization: Bearer abcdefghij0123456789" \
https://api.scripted.com/abcd1234/v1/jobs/5ceb8bb8235bcc76bf475e21/html_contents
GET /:organization_key/v1/jobs/:id/html_contents
To retrieve the actual writing, you have to hit a separate endpoint. It returns an Array of HTML Strings.
Pitchsets
Sample Pitchset
{
"id": "5654ec23a6e02a37e70006f2",
"tagline": "Cool Ideas for Zoo-centric Content",
"status": "active",
"number_of_jobs_requested": 2,
"number_of_pitches_accepted": 0,
"number_of_pitches_rejected": 1,
"number_of_pitches_awaiting_review": 0,
"specialty": {
"id": "5654ebfc93be3b2c623b6ea8",
"name": "Physical Fitness"
},
"guidelines": [
{
"id": "5654ebfc93be3b2c623b6e60",
"name": "1st Personal Singular",
"kind": "Voice"
}
],
"industries": [],
"attachments": [
{
"id": "568da92bfdcedc00e4000000",
"name": "analytics.pptx",
"description": "Use this data in your pitches",
"kind": "Data",
"url": "https://zoos.com/analytics.pptx",
"created_at": "2016-01-06T15:54:19-08:00"
}
],
"prompts": [
{
"id": "5654ec23a6e02a37e70006f6",
"kind": "string[255]",
"label": "Target Audience",
"description": "Describe the particular group at which your content is aimed",
"value": "Kittens of all types: Frat Kittens, Gamer Kittens, Hackysack Kittens, Tech Kittens, and Kitten Kittens",
"answer_required": false
}
],
"content_format": {
"id": "5654ec02a6e02a37e70000d5",
"name": "Standard Blog Post",
"pitchable": true,
"length_metric": "350-450 words",
"quantity_options": [
1
]
}
}
Our writers can pitch you content ideas. A Pitchset is a lot like a Job (it’s forged from a JobTemplate with Prompt answers, Industries and Guidelines), but instead of being a request for writing, it is a request for topics to be pitched by our writers.
status
can be any of the following:
Status | Meaning |
---|---|
active | Writers are generating topic pitches. |
full | Writers have finished pitching ideas. |
marked_complete | You archived the pitchset. |
auto_closed | You accepted as many pitches as you initially requested. |
idle | Writers are no longer pitching topics, because you have not responded to pitches. |
Create a Pitchset
require 'scripted_client'
# First, find a JobTemplate that you'd like to use:
templates = ScriptedClient::JobTemplate.all
blog_post = templates.find { |template| template.name == 'Standard Blog Post' }
# Next, assign some values for the Prompts on that JobTemplate.
key_points = blog_post.prompts.find { |prompt| prompt.label == 'Key Points' }
key_points.value = ['Orangutans make great pets', 'Normal pets are lame']
# Next, you can find an Industry:
industries = ScriptedClient::Industry.all
lifestyle = industries.find { |industry| industry.name == 'Lifestyle & Travel' }
# Now you can create the Pitchset!
pitchset = ScriptedClient::Pitchset.new(
tagline: 'Cool Ideas for Zoo-centric Content',
number_of_jobs_requested: 3,
job_template: blog_post,
industries: [lifestyle]
)
pitchset.save
# => true
curl -H "Authorization: Bearer abcdefghij0123456789" \
https://api.scripted.com/abcd1234/v1/pitchsets \
-d tagline="Cool Ideas for Zoo-centric Content" \
-d number_of_jobs_requested=3 \
-d job_template[id]=5ceb8bb8235bcc76bf475e21 \
-d job_template[prompts][][id]=52cdda2f8b588f7e02000062 \
-d job_template[prompts][][value]=Yes \
-d job_template[prompts][][id]=52cdda2f8b588f7e02000064 \
-d job_template[prompts][][value]=#apifuntimes \
-d guidelines[][id]=e9d378cc8e74b4b5faa34892935d5665 \
-d guidelines[][id]=689ca0246221f2f21e80e9dac9387bce \
-d industries[][id]=8af7c7ae67b6d0854051378b9f3eede4 \
-d industries[][id]=92e4132ea0560de3c12f072dc9a81486 \
-d industries[][id]=f0709aabc7050b3eed7c43db83f0f4cf
POST /:organization_key/v1/pitchsets
Creating a Pitchset is very similar to creating a Job. Pass a JobTemplate and answers for that JobTemplate’s Prompts. Either a list of Industries or a single Specialty must also be included. Guidelines are optional.
Parameter | Description |
---|---|
job_template |
Required Pick a JobTemplate with a ContentFormat whose pitchable is true. |
tagline |
Required Attract and engage writers with a catchy tagline. |
number_of_jobs_requested |
Optional We’ll pitch you twice as many ideas as the number of jobs you request. Defaults to 1 . |
industries |
Required unless Specialty is passed A list of high-level categories pertaining to your job. |
specialty |
Required unless Industries are passed A deeper field of knowledge. |
guidelines |
Optional Tone, Voice and Perspective. |
List all Pitchsets
ScriptedClient::Pitchset.all
# Or if you want to filter by state
ScriptedClient::Pitchset.requires_action
curl -H "Authorization: Bearer abcdefghij0123456789" \
https://api.scripted.com/abcd1234/v1/pitchsets
# Or if you want to filter by state
curl -H "Authorization: Bearer abcdefghij0123456789" \
https://api.scripted.com/abcd1234/v1/pitchsets/requires_action
GET /:organization_key/v1/pitchsets
You can pass any of the following filters to narrow your search:
Filter | Meaning |
---|---|
open | All Pitchsets in active , full , and idle states. The “Active” section of your “Topic Pitches” tab in the Scripted dashboard. |
closed | All Pitchsets in marked_complete and auto_closed states. The “Archived” section of your “Topic Pitches” tab in the Scripted dashboard. |
requires_action | Pitchsets in the open state with at least one pending Pitch. The “Action Items” tab in the Scripted dashboard. |
Show a Pitchset
Don’t forget to replace
5ceb8bb8235bcc76bf475e21
with theID
of one of your pitchsets!
ScriptedClient::Pitchset.find('5ceb8bb8235bcc76bf475e21')
curl -H "Authorization: Bearer abcdefghij0123456789" \
https://api.scripted.com/abcd1234/v1/pitchsets/5ceb8bb8235bcc76bf475e21
GET /:organization_key/v1/pitchsets/:id
Pitches
Sample Pitch
{
"id": "5654ec24a6e02a37e7000772",
"topic": "Ergonomic Cotton Pants",
"body": "What are pants? You'll find out in this thrilling tale of rapid iteration in the garment industry. Wool to Cotton. Warm to Wearable. Economic to Ergonomic. I'll cover everything from the shoes to the belt and leave your readers craving new pants.",
"status": "awaiting review",
"created_at": "2015-11-24T23:00:52Z",
"updated_at": "2016-01-07T17:09:05Z",
"pitchset": {
"id": "5654ec24a6e02a37e7000768",
"tagline": "I'm a Traveling Pants Salesman. Pitch me Content Marketing ideas!"
},
"writer": {
"id": "5654ec23a6e02a37e70006ef",
"nickname": "Jeffrey Lebowski",
"favorite": true
}
}
Pitches are individual topic ideas pitched by Writers. You can accept or reject Pitches. If you accept a Pitch, it becomes a Job, to be completed by the Writer who pitched the idea.
Pitches are nested within Pitchsets, so all actions are namespaced under the URL of the Pitch’s Pitchset.
List all Pitches within a Pitchset
pitchset = ScriptedClient::Pitchset.all.first
pitchset.pitches
curl -H "Authorization: Bearer abcdefghij0123456789" \
https://api.scripted.com/abcd1234/v1/pitchsets/5ceb8bb8235bcc76bf475e21/pitches
GET /:organization_key/v1/pitchsets/:pitchset_id/pitches
Accept a Pitch
pitchset = ScriptedClient::Pitchset.requires_action.first
pitch = pitchset.pitches.first
pitch.accept("This is some optional feedback to the Writer")
curl -H "Authorization: Bearer abcdefghij0123456789" \
https://api.scripted.com/abcd1234/v1/pitchsets/5ceb8bb8235bcc76bf475e21/pitches/5def8bb8235bcc76bf345d5e/accept \
-d feedback="You're just a straight shooter with upper management written all over you!"
POST /:organization_key/v1/pitchsets/:pitchset_id/pitches/:id/accept
You can pass feedback
in the body of the POST
request to accept
a Pitch. Feedback will be passed along to the writer for guidance in writing the Job.
Reject a Pitch
pitchset = ScriptedClient::Pitchset.requires_action.first
pitch = pitchset.pitches.first
pitch.reject("This is some optional feedback to the Writer")
curl -H "Authorization: Bearer abcdefghij0123456789" \
https://api.scripted.com/abcd1234/v1/pitchsets/5ceb8bb8235bcc76bf475e21/pitches/5def8bb8235bcc76bf345d5e/reject \
-d feedback="Not quite what we were looking for. Try making it funnier."
POST /:organization_key/v1/pitchsets/:pitchset_id/pitches/:id/reject
You can pass feedback
in the body of the POST
request to reject
a Pitch. Feedback will be passed along to the writer for guidance in coming up with future topic ideas.
Job Templates
ScriptedClient::JobTemplate.all
curl -H "Authorization: Bearer abcdefghij0123456789" \
https://api.scripted.com/abcd1234/v1/job_templates
Sample JobTemplate
{
"id": "5654ec02a6e02a37e70000cc",
"name": "Standard Blog Post",
"created_at": "2015-11-24T15:00:18-08:00",
"content_format": {
"id": "5654ec02a6e02a37e70000d5",
"name": "Standard Blog Post",
"pitchable": true,
"length_metric": "350-450 words",
"quantity_options": [
1
]
},
"pricing": {
"base": 9900,
"specialist": 14900
},
"prompts": [
{
"id": "5654ec02a6e02a37e70000d8",
"kind": "checkbox",
"label": "Goal",
"description": "Select one or many",
"answer_required": false,
"value_options": [
"Informed analysis",
"Thought leadership",
"Repurpose existing writing",
"Promote topic",
]
},
{
"id": "5654ec02a6e02a37e70000da",
"kind": "string[255]",
"label": "Sample Blog",
"description": "Link to an existing blog and describe why it's a good sample",
"answer_required": false
},
{
"id": "5654ec02a6e02a37e70000dc",
"kind": "checkbox",
"label": "Blog Structure",
"description": "Select one or many",
"answer_required": false,
"value_options": [
"Paragraphs",
"Subheads",
"Lists"
]
},
{
"id": "5654ec02a6e02a37e70000de",
"kind": "array",
"label": "Key Points",
"description": "List key points your writer should address",
"answer_required": false
},
{
"id": "5654ec02a6e02a37e70000e3",
"kind": "radio",
"label": "Links to Sources",
"description": "Select one",
"answer_required": false,
"value_options": [
"Include sources as linked anchor text",
"Include sources as source list",
"No sources"
]
}
]
},
A JobTemplate has a ContentFormat, such as a Short Blog Post, and a collection of Prompts to that are designed to help guide your writer.
Content Formats
Sample ContentFormat
{
"id": "5654ec02a6e02a37e70000d5",
"name": "Standard Blog Post",
"pitchable": true,
"length_metric": "350-450 words",
"quantity_options": [
1
]
}
Embedded within each JobTemplate is a ContentFormat
. By default, the ContentFormats
we offer are: Standard Blog Post, Long Blog Post, Website Page, Article, Facebook Posts, Tweets, Press Release, Video Script, Product Descriptions.
Prompts
Sample Prompt
{
"id": "5654ec02a6e02a37e70000d8",
"kind": "checkbox",
"label": "Goal",
"description": "Select one or many",
"answer_required": false,
"value_options": [
"Informed analysis",
"Thought leadership",
"Repurpose existing writing",
"Promote topic",
]
}
Prompts are question/answer pairs that help guide your writer. They can be one of five kinds: string[255]
string[1024]
radio
checkbox
array
. The data type of the value
that you post will depend on the kind of the Prompt:
Kind | Value Type | Has value_options ? |
---|---|---|
string[255] | String (max. 255 characters) | No |
string[1024] | String (max. 1024 characters) | No |
radio | String | Yes |
checkbox | Array | Yes |
array | Array | No |
Industries
ScriptedClient::Industry.all
curl -H "Authorization: Bearer abcdefghij0123456789" \
https://api.scripted.com/abcd1234/v1/industries
Sample Industry
{
"id": "5654ebfc93be3b2c623b6e2f",
"name": "Publishing & Journalism",
"specialties": [
{
"id": "5654ebfc93be3b2c623b6eab",
"name": "Self Publishing"
}
]
}
GET /:organization_key/v1/industries
Industries specify the topic area of your Job or Pitchset. They also have a list of associated Specialties if you’d like a Writer with more depth of knowledge.
Specialties
ScriptedClient::Specialty.all
curl -H "Authorization: Bearer abcdefghij0123456789" \
https://api.scripted.com/abcd1234/v1/specialties
Sample Specialty
{
"id": "5654ebfc93be3b2c623b6eb1",
"name": "Video Games"
}
GET /:organization_key/v1/specialties
If you’re willing to pay a bit more (see JobTemplate#pricing) for a Specialist writer, you can include a Specialty (instead of a list of Industries) when you create a Job or Pitchset.
Guidelines
ScriptedClient::Guideline.all
curl -H "Authorization: Bearer abcdefghij0123456789" \
https://api.scripted.com/abcd1234/v1/guidelines
Sample Guideline
{
"id": "5654ebfc93be3b2c623b6e63",
"name": "Anecdotal",
"kind": "Tone"
}
GET /:organization_key/v1/guidelines
Guidelines are optional. Jobs and Pitchsets can have many of them.
Pagination
jobs = ScriptedClient::Job.all
jobs.has_next?
# => true
page_two = jobs.next
curl -H "Authorization: Bearer abcdefghij0123456789" \
https://api.scripted.com/abcd1234/v1/jobs \
-d next_cursor='MTQwMDg4OTU0NDoy'
Sample Cursor
{
"data": [{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}],
"paging": {
"has_next": true,
"next_cursor": "MTQwMDg4OTU0NDoy"
}
}
We use cursor-based pagination. A single API request returns 15
records by default. If a list is longer than 15
objects, we’ll return the first page and a next_cursor
that you can pass on your next request to the list endpoint.