Get a project's plan
curl --request GET \
--url https://api.pre.dev/get-plan \
--header 'Authorization: Bearer <token>'import requests
url = "https://api.pre.dev/get-plan"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://api.pre.dev/get-plan', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.pre.dev/get-plan",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.pre.dev/get-plan"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://api.pre.dev/get-plan")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.pre.dev/get-plan")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_body{
"projectId": "507f191e810c19729de860ea",
"specId": null,
"plan": {
"projectId": "507f191e810c19729de860ea",
"name": "Recipe Box",
"computedAt": "2026-10-02T14:00:00.000Z",
"codeVersion": {
"tree": "a36fd496594c6417aa3ab8c6e534f2d7537d4a2e",
"short": "a36fd49"
},
"intent": {
"text": "Recipe Box is for people who keep a small collection of recipes. They can browse recipes, add new ones, and mark a recipe as cooked.",
"proposedByPredev": true,
"removedSince": []
},
"progress": {
"basis": "features",
"built": 2,
"provenWorking": 1,
"total": 3,
"left": 1,
"percent": 67,
"proposed": 0
},
"features": [
{
"id": "feat_1a2b3c4d5e6f7a8b",
"name": "Browse recipes",
"status": "works",
"statusText": "Works, checked 2 h ago",
"scope": "confirmed",
"addedByAgent": false,
"stories": {
"done": 2,
"total": 2
},
"counted": true,
"removedParts": []
},
{
"id": "feat_2b3c4d5e6f7a8b9c",
"name": "Add a recipe",
"status": "built_not_checked",
"statusText": "Built, not checked yet",
"scope": "confirmed",
"addedByAgent": false,
"stories": {
"done": 1,
"total": 1
},
"counted": true,
"removedParts": []
},
{
"id": "feat_3c4d5e6f7a8b9c0d",
"name": "Mark a recipe as cooked",
"status": "planned",
"statusText": "Planned, not built",
"scope": "confirmed",
"addedByAgent": false,
"stories": {
"done": 0,
"total": 1
},
"counted": true,
"removedParts": []
}
],
"readyToLaunch": {
"ready": false,
"items": [
{
"id": "features_built",
"label": "Everything you asked for is built",
"state": "needs_fix",
"detail": "1 of 3 features is not built yet: Mark a recipe as cooked",
"fix": null
},
{
"id": "data_persisted",
"label": "Data is saved beyond one browser",
"state": "ok",
"detail": "Saved in a SQLite file on the server",
"fix": null
},
{
"id": "live_current",
"label": "Not published yet",
"state": "needs_fix",
"detail": null,
"fix": {
"action": "Publish",
"how": "Publish the project from its page on pre.dev."
}
}
]
},
"appMap": {
"codeVersion": "a36fd49",
"mappedAt": "2026-10-02T13:59:00.000Z",
"screens": {
"count": 2,
"items": [
{
"name": "Home",
"path": "/",
"anchor": "server/index.js:22"
},
{
"name": "New Recipe",
"path": "/new",
"anchor": "server/index.js:34"
}
]
},
"endpoints": {
"count": 1,
"items": [
{
"name": "POST /new",
"anchor": "server/index.js:38",
"writes": true
}
]
},
"storage": {
"count": 1,
"items": [
{
"name": "Recipes",
"kind": "table (SQL)",
"where": "in the database",
"anchor": "server/db.js"
}
]
}
},
"lastRun": {
"runId": "6abfb8ac0e1670226a1ecd9b",
"kind": "chat",
"endedAt": "2026-10-02T13:56:00.000Z",
"codeVersion": {
"before": "e82c333",
"after": "a36fd49"
},
"filesChanged": {
"count": 1,
"paths": [
"server/views.js"
]
},
"appMap": {
"added": [
{
"kind": "screen",
"name": "/new"
}
],
"removed": [],
"changed": []
}
}
},
"markdown": "# Plan for Recipe Box\n\nComputed 2026-10-02 14:00 UTC at code version a36fd49.\n\n## What it is for\n\nRecipe Box is for people who keep a small collection of recipes. ...\n\n## Progress\n\n2 of 3 features built (67%), 1 proven working by a check, 1 left.\n..."
}Specs (Architect)
Get a project's plan
What a project is for, each feature’s status from pre.dev’s checks, progress, Ready to launch, and where things live in the code.
GET
/
get-plan
Get a project's plan
curl --request GET \
--url https://api.pre.dev/get-plan \
--header 'Authorization: Bearer <token>'import requests
url = "https://api.pre.dev/get-plan"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://api.pre.dev/get-plan', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.pre.dev/get-plan",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.pre.dev/get-plan"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://api.pre.dev/get-plan")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.pre.dev/get-plan")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_body{
"projectId": "507f191e810c19729de860ea",
"specId": null,
"plan": {
"projectId": "507f191e810c19729de860ea",
"name": "Recipe Box",
"computedAt": "2026-10-02T14:00:00.000Z",
"codeVersion": {
"tree": "a36fd496594c6417aa3ab8c6e534f2d7537d4a2e",
"short": "a36fd49"
},
"intent": {
"text": "Recipe Box is for people who keep a small collection of recipes. They can browse recipes, add new ones, and mark a recipe as cooked.",
"proposedByPredev": true,
"removedSince": []
},
"progress": {
"basis": "features",
"built": 2,
"provenWorking": 1,
"total": 3,
"left": 1,
"percent": 67,
"proposed": 0
},
"features": [
{
"id": "feat_1a2b3c4d5e6f7a8b",
"name": "Browse recipes",
"status": "works",
"statusText": "Works, checked 2 h ago",
"scope": "confirmed",
"addedByAgent": false,
"stories": {
"done": 2,
"total": 2
},
"counted": true,
"removedParts": []
},
{
"id": "feat_2b3c4d5e6f7a8b9c",
"name": "Add a recipe",
"status": "built_not_checked",
"statusText": "Built, not checked yet",
"scope": "confirmed",
"addedByAgent": false,
"stories": {
"done": 1,
"total": 1
},
"counted": true,
"removedParts": []
},
{
"id": "feat_3c4d5e6f7a8b9c0d",
"name": "Mark a recipe as cooked",
"status": "planned",
"statusText": "Planned, not built",
"scope": "confirmed",
"addedByAgent": false,
"stories": {
"done": 0,
"total": 1
},
"counted": true,
"removedParts": []
}
],
"readyToLaunch": {
"ready": false,
"items": [
{
"id": "features_built",
"label": "Everything you asked for is built",
"state": "needs_fix",
"detail": "1 of 3 features is not built yet: Mark a recipe as cooked",
"fix": null
},
{
"id": "data_persisted",
"label": "Data is saved beyond one browser",
"state": "ok",
"detail": "Saved in a SQLite file on the server",
"fix": null
},
{
"id": "live_current",
"label": "Not published yet",
"state": "needs_fix",
"detail": null,
"fix": {
"action": "Publish",
"how": "Publish the project from its page on pre.dev."
}
}
]
},
"appMap": {
"codeVersion": "a36fd49",
"mappedAt": "2026-10-02T13:59:00.000Z",
"screens": {
"count": 2,
"items": [
{
"name": "Home",
"path": "/",
"anchor": "server/index.js:22"
},
{
"name": "New Recipe",
"path": "/new",
"anchor": "server/index.js:34"
}
]
},
"endpoints": {
"count": 1,
"items": [
{
"name": "POST /new",
"anchor": "server/index.js:38",
"writes": true
}
]
},
"storage": {
"count": 1,
"items": [
{
"name": "Recipes",
"kind": "table (SQL)",
"where": "in the database",
"anchor": "server/db.js"
}
]
}
},
"lastRun": {
"runId": "6abfb8ac0e1670226a1ecd9b",
"kind": "chat",
"endedAt": "2026-10-02T13:56:00.000Z",
"codeVersion": {
"before": "e82c333",
"after": "a36fd49"
},
"filesChanged": {
"count": 1,
"paths": [
"server/views.js"
]
},
"appMap": {
"added": [
{
"kind": "screen",
"name": "/new"
}
],
"removed": [],
"changed": []
}
}
},
"markdown": "# Plan for Recipe Box\n\nComputed 2026-10-02 14:00 UTC at code version a36fd49.\n\n## What it is for\n\nRecipe Box is for people who keep a small collection of recipes. ...\n\n## Progress\n\n2 of 3 features built (67%), 1 proven working by a check, 1 left.\n..."
}Every project a spec creates gets a verified plan as soon as the spec completes, and pre.dev keeps it current as the project is built. Read it before you change a project built with pre.dev: it says what is built, what is proven to work, what is left, and where to look in the code.
Pass exactly one of
Add
An organization key reads its organization’s projects; any other key reads only its own. Someone else’s ID returns
A feature’s
A spec that has just completed reads as all
Each status has an example body in the response examples. See errors and retries.
specId (from Fast Spec or Deep Spec) or projectId.
curl --fail-with-body "https://api.pre.dev/get-plan?specId=$SPEC_ID" \
-H "Authorization: Bearer $PREDEV_API_KEY"
format=markdown to receive only the Markdown summary, ready to hand to a coding agent:
curl --fail-with-body "https://api.pre.dev/get-plan?projectId=$PROJECT_ID&format=markdown" \
-H "Authorization: Bearer $PREDEV_API_KEY"
404, the same as an ID that does not exist.
What the plan says
| Field | Meaning |
|---|---|
intent | What the app is for, in one paragraph. proposedByPredev is true until the owner edits it. |
progress | built, provenWorking and total, plus left and percent. basis says what is counted: features, stories for a project whose tasks are not grouped into features yet, or none. Suggested features (proposed) are not counted until the owner confirms them. |
features | Each feature with its status, the words pre.dev shows for it, and its story counts. Work added to a feature that was already built is listed right after that feature with partOf naming it, and counts as its own item until it is built; on a feature, partOf is null. |
readyToLaunch | What is left before the app can go live, each item with a plain fix. |
appMap | Screens, API routes and stored data, each with its file and line (anchor). Each list holds at most 25 items; count is the full number. |
lastRun | The last run that changed the code: the files it changed and what it added, changed or removed. |
codeVersion | The code version the plan was computed at. |
status comes from pre.dev’s checks:
| Status | Meaning |
|---|---|
works | A check of this feature passed at the current code |
built_not_checked | The code is there; nothing has proven it yet |
changed_since_check | Its files changed after its check passed |
needs_a_look | Its check failed the last time it ran |
not_working | Its check keeps failing |
in_progress, planned | Not built yet |
out_of_scope | Set outside the project’s scope; listed, not counted |
removed | Taken out because the owner asked; listed, not counted |
planned, with no appMap or lastRun until code exists. plan is null while a project has nothing planned or built yet. Keys appear by name only (for example, in a Ready to launch item that names a missing key), never their values.
Spec status carries the same plan once a spec is completed, and the MCP server has a matching get_plan tool.
Errors
| Status | When |
|---|---|
400 | Neither or both of specId and projectId, or an ID that is not valid |
401 | Missing or invalid key, or a personal key without a paid plan |
404 | An ID that is not yours, a spec with no project yet, or plans are turned off |
500 | Unexpected error |
Authorizations
apiKeyAuthxApiKey
Your pre.dev workspace API key (pdk_...) from Integrations, Built-in tab: https://pre.dev/projects/integrations?tab=built-in
Query Parameters
A specId from fast-spec or deep-spec.
Pattern:
^[a-fA-F0-9]{24}$A pre.dev project ID.
Pattern:
^[a-fA-F0-9]{24}$markdown returns the Markdown alone as text/markdown.
Available options:
json, markdown Response
The plan, or plan null while the project has none yet.
Pattern:
^[a-fA-F0-9]{24}$Pattern:
^[a-fA-F0-9]{24}$A project's verified plan: what it is for, each feature's status from pre.dev's checks, progress, Ready to launch, where things live in the code, and the last change.
Show child attributes
Show child attributes
The plan as Markdown for a coding agent.
Was this page helpful?

