Create a project
const url = 'https://api.pealboard.com/v1/projects';const options = { method: 'POST', headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'}, body: '{"title":"example","shortDescription":"example","public":true,"statusOptions":["Todo","In progress","Done"],"issueIds":["2489E9AD-2EE2-8E00-8EC9-32D5F69181C0"]}'};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}curl --request POST \ --url https://api.pealboard.com/v1/projects \ --header 'Authorization: Bearer <token>' \ --header 'Content-Type: application/json' \ --data '{ "title": "example", "shortDescription": "example", "public": true, "statusOptions": [ "Todo", "In progress", "Done" ], "issueIds": [ "2489E9AD-2EE2-8E00-8EC9-32D5F69181C0" ] }'Creates a GitHub Project (v2) on the account this workspace is connected to, then caches it with its fields and its items. It is a GitHub project in every respect: it is at github.com, the engineering team can open it there, and deleting Pealboard leaves it behind.
On an organization the Pealboard App creates it. On a personal account it cannot: the App’s Projects permission is an organization permission, and GitHub grants no personal-account equivalent — not to the App, and not to your own linked GitHub account either, because a GitHub App’s user-to-server token carries the App’s permissions rather than your own. 403 before anything reaches GitHub, naming the connected account and the page to create the project on GitHub instead; Pealboard picks it up on the next sync.
statusOptions renames the built-in Status options, which GitHub creates as Todo, In Progress and Done. The names you send are the names GitHub holds. The colours of the first options are kept and any beyond them are given one from GitHub’s palette; the descriptions GitHub writes for its own words are cleared.
public cannot be set by the App. GitHub refuses a visibility change from an installation token whatever the value, so public: true on an organization is a 403 with the remedy rather than a write that silently does nothing. Change it in the project’s own settings on GitHub.
Send Idempotency-Key to make a retry return the first answer rather than creating a second project.
Access. Requires the member role or above (member, admin, owner). A project created here is created in the customer’s own GitHub account, under their installation. API-key scope: projects:write.
Billing. Refused with 402 while the workspace’s billing has lapsed. Reads continue and nothing is deleted.
Retries. Send Idempotency-Key and a retry with the same key within 24 hours returns the first answer rather than acting twice.
Authorizations
Section titled “Authorizations”Request Body
Section titled “Request Body”object
The line under the title on GitHub’s own project list. GitHub ignores a blank one, so an empty string is refused here rather than accepted and dropped.
Refused with 403 when Pealboard is writing as the App, which is every organization installation. GitHub does not let an App change a project’s visibility.
Two to ten names, all different. Renames the built-in Status options. Omit to keep GitHub’s Todo, In Progress and Done.
Example
[ "Todo", "In progress", "Done"]Issues to put on the project, from GET /v1/issues. Each becomes an item; the field values a project’s built-in workflow sets on add arrive with the projects_v2_item webhook.
Responses
Section titled “Responses”The project, as GET /v1/projects/{projectId} returns it.
object
A UUID this API issued.
The project on GitHub.
object
A UUID this API issued.
What every field value is keyed by. Survives a rename.
GitHub’s ProjectV2FieldType: TEXT, SINGLE_SELECT, NUMBER, DATE, ITERATION, and the rest.
In the order the project shows them. Null unless the field is a single select.
object
True when the field is an organization issue field linked into this project.
Examplegenerated
{ "id": "2489E9AD-2EE2-8E00-8EC9-32D5F69181C0", "nodeId": "example", "number": 1, "title": "example", "shortDescription": "example", "ownerLogin": "example", "public": true, "closed": true, "url": "https://example.com", "fields": [ { "id": "2489E9AD-2EE2-8E00-8EC9-32D5F69181C0", "nodeId": "example", "name": "example", "dataType": "example", "options": [ { "id": "example", "name": "example", "color": "example", "description": "example" } ], "isIssueField": true } ], "itemCount": 1, "syncedAt": "2026-04-15T12:00:00Z"}No valid credentials.
object
object
A stable identifier for the kind of failure. Safe to branch on.
What went wrong, written for a person.
Structured particulars, when there are any.
object
What to do about it, when there is something to do.
Quote this when asking about a specific failure.
Examplegenerated
{ "error": { "code": "example", "message": "example", "detail": { "additionalProperty": "example" }, "remedy": "example", "requestId": "example" }}The workspace’s billing has lapsed, so writes are refused.
object
object
A stable identifier for the kind of failure. Safe to branch on.
What went wrong, written for a person.
Structured particulars, when there are any.
object
What to do about it, when there is something to do.
Quote this when asking about a specific failure.
Examplegenerated
{ "error": { "code": "example", "message": "example", "detail": { "additionalProperty": "example" }, "remedy": "example", "requestId": "example" }}Authenticated, but not permitted.
object
object
A stable identifier for the kind of failure. Safe to branch on.
What went wrong, written for a person.
Structured particulars, when there are any.
object
What to do about it, when there is something to do.
Quote this when asking about a specific failure.
Examplegenerated
{ "error": { "code": "example", "message": "example", "detail": { "additionalProperty": "example" }, "remedy": "example", "requestId": "example" }}No such resource, or it is not visible to you.
object
object
A stable identifier for the kind of failure. Safe to branch on.
What went wrong, written for a person.
Structured particulars, when there are any.
object
What to do about it, when there is something to do.
Quote this when asking about a specific failure.
Examplegenerated
{ "error": { "code": "example", "message": "example", "detail": { "additionalProperty": "example" }, "remedy": "example", "requestId": "example" }}The request conflicts with the current state.
object
object
A stable identifier for the kind of failure. Safe to branch on.
What went wrong, written for a person.
Structured particulars, when there are any.
object
What to do about it, when there is something to do.
Quote this when asking about a specific failure.
Examplegenerated
{ "error": { "code": "example", "message": "example", "detail": { "additionalProperty": "example" }, "remedy": "example", "requestId": "example" }}The request did not match the expected shape.
object
object
A stable identifier for the kind of failure. Safe to branch on.
What went wrong, written for a person.
Structured particulars, when there are any.
object
What to do about it, when there is something to do.
Quote this when asking about a specific failure.
Examplegenerated
{ "error": { "code": "example", "message": "example", "detail": { "additionalProperty": "example" }, "remedy": "example", "requestId": "example" }}Rate limited. Retry after the interval in the header.
object
object
A stable identifier for the kind of failure. Safe to branch on.
What went wrong, written for a person.
Structured particulars, when there are any.
object
What to do about it, when there is something to do.
Quote this when asking about a specific failure.
Examplegenerated
{ "error": { "code": "example", "message": "example", "detail": { "additionalProperty": "example" }, "remedy": "example", "requestId": "example" }}GitHub or another dependency refused or did not answer. Nothing in the workspace changed.
object
object
A stable identifier for the kind of failure. Safe to branch on.
What went wrong, written for a person.
Structured particulars, when there are any.
object
What to do about it, when there is something to do.
Quote this when asking about a specific failure.
Examplegenerated
{ "error": { "code": "example", "message": "example", "detail": { "additionalProperty": "example" }, "remedy": "example", "requestId": "example" }}