Skip to content
Niftyhelp 2026.10.1-beta.1 Support

Beta This is the help for Nifty 2026.10.1-beta.1, which isn't released yet. Help for the current release

Calendar

Events from your calendar feeds.

On this page

Get a range of the calendar

GET/calendar

Everything dated from from to to (inclusive, in the owner's time zone): the calendar feeds' events, expanded from their repeat rules, and Nifty's own dates (tasks due, project deadlines and starts, key dates). A view, not a record: there's nothing to page or sync, so ask again when things change. Expired tasks are archived first.

  • events: each occurrence touching the range (a timed event that started before from but is still on, a multi-day all-day event), by start, all-day ones first on a day; then title, then key.
  • tasks: live tasks, open or completed, whose deadline_on is in the range (an overdue open task only on its own date), by deadline_on, then title.
  • project_dates: live projects that are planned, active or on_hold, with a deadline or start_date in the range, by date, then name.
  • key_dates: live people's key dates falling in the range (29 February on 1 March in other years), by date, then person name, then kind.
  • people: the people the events' attendees are, by name.
curl "$NIFTY_URL/api/v1/calendar?from=2026-10-10&to=2026-10-10" \
  -H "Authorization: Bearer $NIFTY_TOKEN"

Query parameters

NameDescription
fromdateThe first day, YYYY-MM-DD.
todateThe last day, YYYY-MM-DD; on or after from, and at most 42 days in all. Otherwise 400.

Response

200 OK Errors: 400 401 429

{
  "data": {
    "from": "2026-10-10",
    "to": "2026-10-10",
    "today": "2026-10-10",
    "events": [
      {
        "key": "string",
        "feed_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
        "title": "Team stand-up",
        "all_day": true,
        "starts_at": "2026-10-10T09:30:00Z",
        "ends_at": "2026-10-10T09:30:00Z",
        "start_on": "2026-10-10",
        "end_on": "2026-10-10",
        "location": "Room 2",
        "description": "Weekly check-in.",
        "url": "string",
        "meeting": {
          "provider": "google_meet",
          "url": "https://example.com"
        },
        "attendee_count": 0,
        "attendee_names": [
          "string"
        ],
        "attendees": [
          {
            "name": "Maya Okafor",
            "email": "[email protected]",
            "organizer": true,
            "person_id": "01j9zq3k8m5x2v7c4n6b0t1r9e"
          }
        ],
        "rrule": "FREQ=WEEKLY;BYDAY=MO,WE"
      }
    ],
    "tasks": [
      {
        "id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
        "title": "Book the plumber",
        "status": "open",
        "start_on": "2026-10-10",
        "deadline_on": "2026-10-10",
        "expires_on": "2026-10-10",
        "completed_at": "2026-10-10T09:30:00Z",
        "archived_at": "2026-10-10T09:30:00Z",
        "task_list_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
        "person_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
        "project_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
        "position": 1,
        "description_html": "<p>Ask about the boiler too.</p>",
        "description_text": "Ask about the boiler too.",
        "has_documents": true,
        "expired": true,
        "created_at": "2026-10-10T09:30:00Z",
        "updated_at": "2026-10-10T09:30:00Z"
      }
    ],
    "project_dates": [
      {
        "date": "2026-10-10",
        "kind": "deadline",
        "project": {
          "id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
          "name": "Kitchen renovation",
          "status": "planned",
          "status_changed_at": "2026-10-10T09:30:00Z",
          "start_date": "2026-10-10",
          "deadline": "2026-10-10",
          "my_role": "Organiser",
          "external_reference": "JIRA-123",
          "external_url": "https://example.com",
          "description_html": "<p>New units and worktops.</p>",
          "description_text": "New units and worktops.",
          "members": [
            {
              "id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
              "person_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
              "role": "string"
            }
          ],
          "created_at": "2026-10-10T09:30:00Z",
          "updated_at": "2026-10-10T09:30:00Z"
        }
      }
    ],
    "key_dates": [
      {
        "date": "2026-10-10",
        "years": 1,
        "key_date": {
          "id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
          "kind": "birthday",
          "label": "Graduation",
          "day": 1,
          "month": 1,
          "year": 1,
          "created_at": "2026-10-10T09:30:00Z",
          "updated_at": "2026-10-10T09:30:00Z"
        },
        "person": {
          "id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
          "name": "Maya Okafor",
          "avatar": {
            "url": "https://example.com",
            "thumb_url": "https://example.com",
            "large_url": "https://example.com"
          },
          "relationships": [
            "spouse"
          ]
        }
      }
    ],
    "people": [
      {
        "id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
        "name": "Maya Okafor",
        "avatar": {
          "url": "https://example.com",
          "thumb_url": "https://example.com",
          "large_url": "https://example.com"
        },
        "key_dates": [
          {
            "id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
            "kind": "birthday",
            "label": "Graduation",
            "day": 1,
            "month": 1,
            "year": 1,
            "created_at": "2026-10-10T09:30:00Z",
            "updated_at": "2026-10-10T09:30:00Z"
          }
        ],
        "email_addresses": [
          {
            "id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
            "address": "[email protected]",
            "label": "Work",
            "created_at": "2026-10-10T09:30:00Z",
            "updated_at": "2026-10-10T09:30:00Z"
          }
        ],
        "phone_numbers": [
          {
            "id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
            "number": "+44 20 7946 0958",
            "label": "Mobile",
            "created_at": "2026-10-10T09:30:00Z",
            "updated_at": "2026-10-10T09:30:00Z"
          }
        ],
        "social_profiles": [
          {
            "id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
            "url": "https://example.com",
            "platform": "website",
            "platform_detected": true,
            "label": "Blog",
            "created_at": "2026-10-10T09:30:00Z",
            "updated_at": "2026-10-10T09:30:00Z"
          }
        ],
        "positions": [
          {
            "id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
            "title": "Head of Design",
            "company_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
            "created_at": "2026-10-10T09:30:00Z",
            "updated_at": "2026-10-10T09:30:00Z"
          }
        ],
        "relationships": [
          "spouse"
        ],
        "connections": [
          {
            "id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
            "person_id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
            "kind": "spouse",
            "note": "Met at university"
          }
        ],
        "created_at": "2026-10-10T09:30:00Z",
        "updated_at": "2026-10-10T09:30:00Z"
      }
    ]
  }
}

List calendar feeds

GET/calendar_feeds

The iCal addresses whose events the calendar shows. The address itself is never returned.

curl "$NIFTY_URL/api/v1/calendar_feeds" \
  -H "Authorization: Bearer $NIFTY_TOKEN"

Query parameters

NameDescription
ordername | id | updated_at · optionalname sorts ignoring case and accents, with id as the tie-breaker.
directionasc | desc · optionalasc (ascending) or desc (descending), for order.
updated_sincedate-time · optionalOnly records updated at or after this time (ISO 8601 with Z or a UTC offset), ordered by updated_at, id.
cursorstring · optionalmeta.next_cursor from the previous page.
limitinteger · optionalItems per page, up to 100; a larger number is taken as 100.
counttrue | false · optionaltrue adds meta.total_count: the size of the filtered set (with updated_since too), ignoring cursor and limit. Anything but true or false is a 400.

Response

200 OK Errors: 400 401 429

{
  "data": [
    {
      "id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
      "name": "Work",
      "color": "teal",
      "address_host": "calendar.google.com",
      "source": "google",
      "sync_every": "30_minutes",
      "last_synced_at": "2026-10-10T09:30:00Z",
      "last_attempt_at": "2026-10-10T09:30:00Z",
      "sync_error": {
        "code": "not_found",
        "http_status": 100,
        "failures": 1
      },
      "created_at": "2026-10-10T09:30:00Z",
      "updated_at": "2026-10-10T09:30:00Z"
    }
  ],
  "meta": {
    "next_cursor": "eyJrIjpbIjIwMjYtMTAtMDlUMDg6MzA6MDBaIl19",
    "limit": 1,
    "total_count": 1
  }
}

Add a calendar feed

POST/calendar_feeds

Fetches the address first (waiting up to 30 seconds), then saves the feed with its events. An address that can't be read is a 422 on address saying why ("Address couldn't be read: the server says there's no calendar there (404)"), and nothing is saved. A blank name takes the calendar's own (X-WR-CALNAME), else the address's host.

curl -X POST "$NIFTY_URL/api/v1/calendar_feeds" \
  -H "Authorization: Bearer $NIFTY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"calendar_feed":{"name":"Work","address":"https://calendar.example.com/feeds/work.ics","color":"blue","sync_every":"hourly"}}'

Headers

NameDescription
Idempotency-Keystring · optionalMakes a POST safe to retry for 24 hours. Needs a bearer token (400 under session auth).

Request body

NameDescription
calendar_feedobject
calendar_feed.namestring or null · optionalSpaces are squished. Unique ignoring case. Blank on create takes the calendar's own name, else the address's host; blank on update is a 422.
calendar_feed.addressstring · optionalRequired on create. An https://, http:// or webcal:// iCal address (webcal is fetched over https). Write-only: stored encrypted, never returned; only its host is (address_host).
calendar_feed.colorstring · optionalA key from the vocabulary's colors. Required on create.
calendar_feed.sync_everyCalendarSyncFrequency · optionalHow often the feed is fetched; the words are the vocabulary's calendar_sync_frequencies.

Response

201 Created Errors: 400 401 403 409 415 422 429

{
  "data": {
    "id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
    "name": "Work",
    "color": "teal",
    "address_host": "calendar.google.com",
    "source": "google",
    "sync_every": "30_minutes",
    "last_synced_at": "2026-10-10T09:30:00Z",
    "last_attempt_at": "2026-10-10T09:30:00Z",
    "sync_error": {
      "code": "not_found",
      "http_status": 100,
      "failures": 1
    },
    "created_at": "2026-10-10T09:30:00Z",
    "updated_at": "2026-10-10T09:30:00Z"
  }
}

Sync every calendar feed now

POST/calendar_feeds/sync

Fetches every feed (up to 4 at once, each waiting up to 30 seconds), ignoring their schedules and backoff, and returns them all, by name. A feed that couldn't be read is still in the 200, with sync_error set. Takes no body; ignores Idempotency-Key.

curl -X POST "$NIFTY_URL/api/v1/calendar_feeds/sync" \
  -H "Authorization: Bearer $NIFTY_TOKEN"

Response

200 OK Errors: 400 401 403 415 429

{
  "data": [
    {
      "id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
      "name": "Work",
      "color": "teal",
      "address_host": "calendar.google.com",
      "source": "google",
      "sync_every": "30_minutes",
      "last_synced_at": "2026-10-10T09:30:00Z",
      "last_attempt_at": "2026-10-10T09:30:00Z",
      "sync_error": {
        "code": "not_found",
        "http_status": 100,
        "failures": 1
      },
      "created_at": "2026-10-10T09:30:00Z",
      "updated_at": "2026-10-10T09:30:00Z"
    }
  ]
}

Get a calendar feed

GET/calendar_feeds/{id}

curl "$NIFTY_URL/api/v1/calendar_feeds/01j9zq3k8m5x2v7c4n6b0t1r9e" \
  -H "Authorization: Bearer $NIFTY_TOKEN"

Path parameters

NameDescription
idULIDA record's ID.

Response

200 OK Errors: 401 404 429

{
  "data": {
    "id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
    "name": "Work",
    "color": "teal",
    "address_host": "calendar.google.com",
    "source": "google",
    "sync_every": "30_minutes",
    "last_synced_at": "2026-10-10T09:30:00Z",
    "last_attempt_at": "2026-10-10T09:30:00Z",
    "sync_error": {
      "code": "not_found",
      "http_status": 100,
      "failures": 1
    },
    "created_at": "2026-10-10T09:30:00Z",
    "updated_at": "2026-10-10T09:30:00Z"
  }
}

Change a calendar feed

PATCH/calendar_feeds/{id}

Also PUT /calendar_feeds/{id}, the same.

Only the fields sent change. A new address is fetched first, as on create (a 422 when it can't be read, and nothing changes); the name, colour and frequency save without a fetch. A new sync_every moves the next sync.

curl -X PATCH "$NIFTY_URL/api/v1/calendar_feeds/01j9zq3k8m5x2v7c4n6b0t1r9e" \
  -H "Authorization: Bearer $NIFTY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"calendar_feed":{"name":"Work","address":"https://calendar.example.com/feeds/work.ics","color":"blue","sync_every":"hourly"}}'

Path parameters

NameDescription
idULIDA record's ID.

Request body

NameDescription
calendar_feedobject
calendar_feed.namestring or null · optionalSpaces are squished. Unique ignoring case. Blank on create takes the calendar's own name, else the address's host; blank on update is a 422.
calendar_feed.addressstring · optionalRequired on create. An https://, http:// or webcal:// iCal address (webcal is fetched over https). Write-only: stored encrypted, never returned; only its host is (address_host).
calendar_feed.colorstring · optionalA key from the vocabulary's colors. Required on create.
calendar_feed.sync_everyCalendarSyncFrequency · optionalHow often the feed is fetched; the words are the vocabulary's calendar_sync_frequencies.

Response

200 OK Errors: 400 401 403 404 415 422 429

{
  "data": {
    "id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
    "name": "Work",
    "color": "teal",
    "address_host": "calendar.google.com",
    "source": "google",
    "sync_every": "30_minutes",
    "last_synced_at": "2026-10-10T09:30:00Z",
    "last_attempt_at": "2026-10-10T09:30:00Z",
    "sync_error": {
      "code": "not_found",
      "http_status": 100,
      "failures": 1
    },
    "created_at": "2026-10-10T09:30:00Z",
    "updated_at": "2026-10-10T09:30:00Z"
  }
}

Remove a calendar feed

DELETE/calendar_feeds/{id}

Permanent; its events leave the calendar (the calendar itself isn't changed). Writes a calendar_feed deletion record.

curl -X DELETE "$NIFTY_URL/api/v1/calendar_feeds/01j9zq3k8m5x2v7c4n6b0t1r9e" \
  -H "Authorization: Bearer $NIFTY_TOKEN"

Path parameters

NameDescription
idULIDA record's ID.

Response

204 Deleted Errors: 401 403 404 429

Sync a calendar feed now

POST/calendar_feeds/{calendar_feed_id}/sync

Fetches the feed now (waiting up to 30 seconds), ignoring its schedule and backoff. A feed that couldn't be read is still a 200, with sync_error set. Takes no body; ignores Idempotency-Key.

curl -X POST "$NIFTY_URL/api/v1/calendar_feeds/01j9zq3k8m5x2v7c4n6b0t1r9e/sync" \
  -H "Authorization: Bearer $NIFTY_TOKEN"

Path parameters

NameDescription
calendar_feed_idULIDA record's ID.

Response

200 OK Errors: 400 401 403 404 415 429

{
  "data": {
    "id": "01j9zq3k8m5x2v7c4n6b0t1r9e",
    "name": "Work",
    "color": "teal",
    "address_host": "calendar.google.com",
    "source": "google",
    "sync_every": "30_minutes",
    "last_synced_at": "2026-10-10T09:30:00Z",
    "last_attempt_at": "2026-10-10T09:30:00Z",
    "sync_error": {
      "code": "not_found",
      "http_status": 100,
      "failures": 1
    },
    "created_at": "2026-10-10T09:30:00Z",
    "updated_at": "2026-10-10T09:30:00Z"
  }
}

Menu

2026.10.1-beta.1Contact support