REST API v1

Everything the app does, you can do over HTTP. Scoped keys, predictable JSON, cursor pagination and signed webhooks — on every plan, including Free.

Base URL

https://manageyourproject.net/api/v1

Authentication

Create a key in Settings → API. Keys are shown once; we store only a hash. Choose the narrowest scope that works: read, write or admin.

curl https://manageyourproject.net/api/v1/tasks \
  -H "X-Api-Key: myp_live_2f8c...";

A bearer token is also accepted, which is handy for OAuth-style integrations:

curl https://manageyourproject.net/api/v1/tasks -H "Authorization: Bearer myp_live_2f8c..."

Conventions

  • JSON in, JSON out. Send Content-Type: application/json on writes.
  • PATCH is a partial update; omitted fields are untouched.
  • Timestamps are UTC ISO-8601. Dates are YYYY-MM-DD.
  • Every response carries X-Request-Id. Quote it in support requests.
  • IDs are integers, scoped to your organisation. There is no way to read another tenant's row.

Pagination

Cursor-based, because offsets drift while people are working.

GET https://manageyourproject.net/api/v1/tasks?limit=100&cursor=eyJpZCI6MTgzMn0

{
  "data": [ { "id": 1833, "title": "..." } ],
  "meta": { "count": 100, "has_more": true, "next_cursor": "eyJpZCI6MTkzM30" }
}

Pass updated_since to build an incremental sync. Combined with webhooks that gives you a reliable mirror without polling everything.

Filtering

GET https://manageyourproject.net/api/v1/tasks
  ?project_id=12
  &status_category=in_progress
  &assignee_id=8
  &due_before=2026-08-26
  &tag=billable
  &sort=-priority,due_date

Resources

ResourceEndpointsNotes
/projectsGET, POST, PATCH, DELETEInclude ?with=stats for task and time rollups
/tasksGET, POST, PATCH, DELETEAccepts assignees[], tags[], custom_fields{}
/tasks/{id}/commentsGET, POSTMentions resolve from @email
/time-entriesGET, POST, PATCH, DELETEduration_min or started_at/ended_at
/documentsGET, POST, PATCHHTML body is sanitised server-side
/invoicesGET, POST, PATCHPOST /invoices/from-time bills unbilled hours
/clientsGET, POST, PATCHReturns the portal URL when one exists
/goalsGET, POST, PATCHProgress is derived from linked work
/usersGETMembers of your organisation only
/webhooksGET, POST, DELETEReturns the signing secret once, on create

Create a task

curl -X POST https://manageyourproject.net/api/v1/tasks \
  -H "X-Api-Key: $KEY" -H "Content-Type: application/json" \
  -d '{
    "project_id": 12,
    "title": "Instrument the checkout funnel",
    "description": "<p>Events for view, add, purchase.</p>",
    "priority": 3,
    "estimated_hours": 8,
    "due_date": "2026-08-22",
    "assignees": [8, 14],
    "tags": ["analytics", "billable"],
    "custom_fields": { "client_ref": "ACME-2211" }
  }'

Errors

{
  "error": {
    "code": "validation_failed",
    "message": "due_date must be on or after start_date",
    "field": "due_date"
  }
}
StatusCodeMeaning
400bad_requestMalformed JSON or unknown parameter
401unauthenticatedMissing, revoked or malformed key
403forbiddenKey scope or workspace role is insufficient
403plan_limitPlan entitlement exceeded; body names the limit
404not_foundNo such record in your organisation
422validation_failedField-level validation error
429rate_limitedRetry after the seconds in Retry-After

Rate limits

Per key, per minute: 60 on Free, 300 on Pro, 1,000 on Business, 5,000 on Enterprise. Every response includes X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset. Back off on 429 rather than retrying immediately — repeated hammering extends the window.

Webhooks

Register an endpoint, pick events, verify the signature. Payloads are delivered within seconds and retried with exponential backoff for 24 hours; an endpoint failing continuously is disabled and you are emailed.

POST /webhooks
{
  "url": "https://example.com/hooks/myp",
  "events": ["task.created", "task.status_changed", "time.logged", "invoice.paid"]
}

Verify in PHP:

$raw = file_get_contents('php://input');
$sig = $_SERVER['HTTP_X_MYP_SIGNATURE'] ?? '';
$ts  = $_SERVER['HTTP_X_MYP_TIMESTAMP'] ?? '';

if (abs(time() - (int) $ts) > 300) {
    http_response_code(400);
    exit; // replay window exceeded
}

$expected = hash_hmac('sha256', $ts . '.' . $raw, $secret);
if (!hash_equals($expected, $sig)) {
    http_response_code(401);
    exit;
}

http_response_code(200); // ack fast, then process in a queue

Idempotency

Send Idempotency-Key on POST and we return the original response for 24 hours instead of creating a duplicate. Use it whenever a retry is possible, which is always.

Client libraries

Generate one from the OpenAPI 3 spec:

npx @openapitools/openapi-generator-cli generate \
  -i https://manageyourproject.net/openapi.json \
  -g typescript-fetch -o ./src/myp