Rows changed since a version
sync.pull
GET/api/v1/households/{household_id}/pull
- Route
Every synced row with a version greater than from, and every row deleted since. Send 0 for everything. A device also sends its client_id and after_seq (the highest sequence number it knows the household processed) and gets back what happened to its own pushed commands, the highest sequence number processed, and the household's schema version, which a device with fewer migrations must update to match. See the sync guide.
Parameters#
| Field | Description |
|---|---|
household_idpathstringrequired | The household's ID. |
fromqueryinteger or nulloptional | The household version you have. |
client_idquerystringoptional | A device's ID. Give it to also get the outcomes of that device's pushed commands. |
after_seqqueryinteger or nulloptional | With client_id: report only this device's commands after this sequence number. |
Response#
200 The changes; with client_id, also the device's outcomes.
| Field | Description |
|---|---|
| Shape 1, with last_client_seq, outcomes, schema_version | |
fromintegerrequired | The version the caller had. |
versionintegerrequired | The household version these rows bring the caller up to. |
rowsmap of arrays of objectsrequired | Changed rows by synced table name, in reference order. |
tombstonesarray of objectsrequired | Rows deleted since |
tombstones[].table_namestringrequired | No description. |
tombstones[].row_idstringrequired | No description. |
last_client_seqintegerrequired | The highest of this device's commands the server has processed, or 0. |
outcomesarray of objectsrequired | This device's commands processed after after_seq, in order. |
outcomes[].client_seqintegerrequired | The device's sequence number for the command. |
outcomes[].command_idstringrequired | The command's ID, as the device sent it. |
outcomes[].statusone of: applied, rejectedrequired | Whether the household applied the command or rejected it. |
outcomes[].errorobject or nullrequired | Why a rejected command was rejected; null when it was applied. |
outcomes[].error.codestringrequired | Stable snake_case code, such as person_not_found. |
outcomes[].error.messagestringrequired | What went wrong, written for the person who issued the command. |
schema_versionintegerrequired | How many household migrations the server has applied. |
| Shape 2 | |
fromintegerrequired | The version the caller had. |
versionintegerrequired | The household version these rows bring the caller up to. |
rowsmap of arrays of objectsrequired | Changed rows by synced table name, in reference order. |
tombstonesarray of objectsrequired | Rows deleted since |
tombstones[].table_namestringrequired | No description. |
tombstones[].row_idstringrequired | No description. |
Errors#
Every error has the same envelope: error.code, error.message, error.details, error.recovery and error.request_id.
| Status | Code | When |
|---|---|---|
400 | validation_failed | A field is missing, misspelled or out of range. details.fields lists each problem. |
401 | unauthenticated | The request has no valid session. |
403 | Read error.code | Described above. |
404 | not_found | The household doesn't exist, or you aren't a member of it. |
Examples#
These are real. Each was recorded from a sample household, the Okafors, running through the whole API when this site was built.
Everything in the household
Every synced row with a version above from, and every row deleted since. Send 0 for everything.
curl 'https://rigel.house/api/v1/households/hh_01M6V9CS6GGVFCY7NAS8ZTQV95/pull?from=0' \
-H "Cookie: rigel_session=$RIGEL_SESSION"const response = await fetch("https://rigel.house/api/v1/households/hh_01M6V9CS6GGVFCY7NAS8ZTQV95/pull?from=0", {
headers: {
Cookie: `rigel_session=${process.env.RIGEL_SESSION}`,
},
});
const data = await response.json();GET /api/v1/households/hh_01M6V9CS6GGVFCY7NAS8ZTQV95/pull?from=0 HTTP/1.1
Host: rigel.house
Cookie: rigel_session=<your session>HTTP/1.1 200 OK
Content-Type: application/json
X-Request-Id: req_01M6V9CS813K32SXJ9KAXJ3W7P
{
"from": 0,
"version": 13,
"rows": {
"file": [],
"person": [
{
"id": "per_01M6V9CS6RX26JK5KVYGRN22D2",
"created_at": 1794064671948,
"updated_at": 1794064671948,
"version": 1,
"name": "Ada Okafor",
"birth_date": null,
"birth_date_precision": null,
"user_id": "usr_01M6V9CS5XV66GR856KS52MTTZ",
"sort_order": 0
},
{
"id": "per_01M6V9CS744BQQH188FS6CXY9W",
"created_at": 1794064671976,
"updated_at": 1794064671976,
"version": 2,
"name": "Ben Okafor",
"birth_date": null,
"birth_date_precision": null,
"user_id": null,
"sort_order": 1
},
{
"id": "per_01M6V9CS79TMYHDAM7FTA9ZK3Z",
"created_at": 1794064671979,
"updated_at": 1794064671979,
"version": 3,
"name": "Nia",
"birth_date": "2018-03-14",
"birth_date_precision": "day",
"user_id": null,
"sort_order": 2
},
{
"id": "per_01M6V9CS7CDAEP5VDA5T8R9TG6",
"created_at": 1794064671980,
"updated_at": 1794064671980,
"version": 4,
"name": "Theo",
"birth_date": "2021-08-01",
"birth_date_precision": "month",
"user_id": null,
"sort_order": 3
}
],
"family_note": [
{
"id": "fnt_01M6V9CS7GXFHGYGZ4CFZG4J38",
"created_at": 1794064671985,
"updated_at": 1794064671985,
"version": 6,
"person_id": "per_01M6V9CS79TMYHDAM7FTA9ZK3Z",
"text": "Nia wants to learn to bake. She likes measuring the flour.",
"app": "recipes",
"noted_at": 1794064671985
},
{
"id": "fnt_01M6V9CS7HSE0X6FSYVAC690KF",
"created_at": 1794064671986,
"updated_at": 1794064671986,
"version": 7,
"person_id": "per_01M6V9CS744BQQH188FS6CXY9W",
"text": "Ben doesn't eat shellfish.",
"app": "meals",
"noted_at": 1794064671986
},
{
"id": "fnt_01M6V9CS7MX4PM9SC6Z6NVA2EA",
"created_at": 1794064671989,
"updated_at": 1794064671989,
"version": 9,
"person_id": null,
"text": "Saturday mornings are for pancakes.",
"app": null,
"noted_at": 1794064671989
},
{
"id": "fnt_01M6V9CS7E9NAJKGNWG7KBC8P4",
"created_at": 1794064671983,
"updated_at": 1794064671990,
"version": 10,
"person_id": "per_01M6V9CS7CDAEP5VDA5T8R9TG6",
"text": "Theo isn't into spicy food lately. Mild chili is fine.",
"app": "meals",
"noted_at": 1794064671990
},
{
"id": "fnt_01M6V9CS7KC15H3QT502N0J25K",
"created_at": 1794064671988,
"updated_at": 1794064671992,
"version": 11,
"person_id": null,
"text": "Dinner is at 6:00 pm on weeknights.",
"app": null,
"noted_at": 1794064671988
}
],
"integration": [],
"notification": [],
"notification_mute": [],
"folder": [],
"document": [],
"document_body": [],
"document_extract": [],
"document_version": [],
"document_link": [],
"document_import": [],
"storage_location": [],
"product_category": [],
"product": [],
"recipe": [],
"recipe_ingredient": [],
"recipe_step": [],
"recipe_step_ingredient": [],
"inventory_item": [],
"dashboard_card": [],
"app_picture": [],
"agent_routine": [],
"task": [],
"task_dependency": [],
"tag": [],
"task_tag": [],
"task_comment": [],
"task_comment_mention": [],
"task_comment_reaction": [],
"task_watch": [],
"task_link": [],
"task_view": [],
"task_view_value": [],
"task_view_tag": [],
"agent_trigger": [],
"dish": [],
"dish_ingredient": [],
"meal": [],
"meal_person": [],
"meal_dish": [],
"dish_combo_icon": [],
"dish_combo_icon_dish": [],
"meals_setting": [],
"email_domain": [],
"email_address": [],
"email_domain_record": [],
"email_correspondent": [],
"email_thread": [],
"email_connection": [],
"email_connection_rule": [],
"email_message": [],
"email_message_recipient": [],
"email_attachment": [],
"email_action": [],
"email_draft": [],
"email_draft_recipient": [],
"email_draft_attachment": [],
"recipe_cook": [],
"calendar": [
{
"id": "cal_01M6V9CS6R7BJ7D0KVCQQH1DCS",
"created_at": 1794064671948,
"updated_at": 1794064671948,
"version": 1,
"name": "Ada Okafor",
"color": "calendar-2",
"person_id": "per_01M6V9CS6RX26JK5KVYGRN22D2",
"is_default": 0,
"is_busy_only": 0,
"is_visible": 1,
"sort_order": 1
},
{
"id": "cal_01M6V9CS6RESEHY2KVBGF3JX5Q",
"created_at": 1794064671948,
"updated_at": 1794064671948,
"version": 1,
"name": "Family",
"color": "calendar-1",
"person_id": null,
"is_default": 1,
"is_busy_only": 0,
"is_visible": 1,
"sort_order": 0
}
],
"calendar_event": [],
"calendar_event_exception": [],
"calendar_event_date": [],
"calendar_event_attachment": [],
"calendar_event_person": [],
"calendar_event_reminder": [],
"google_calendar": [],
"google_calendar_link": [],
"conversation": [],
"custom_emoji": [],
"sticker": [],
"message": [],
"message_attachment": [],
"message_mention": [],
"message_link": [],
"message_reaction": [],
"expression_pack": [],
"expression_pack_item": [],
"conversation_read": [],
"conversation_follow": [],
"sample_record": [],
"property": [],
"maintenance_location": [],
"category": [],
"model": [],
"evidence": [],
"asset": [],
"asset_photo": [],
"attribute": [],
"identification": [],
"identification_alternative": [],
"part": [],
"model_part": [],
"asset_part": [],
"warranty": [],
"contractor": [],
"contractor_contact": [],
"maintenance_task": [],
"maintenance_task_part": [],
"maintenance_project": [],
"maintenance_project_asset": [],
"maintenance_project_update": [],
"issue": [],
"visit": [],
"visit_asset": [],
"visit_issue": [],
"maintenance_task_completion": [],
"invoice": [],
"invoice_line": [],
"ongoing_service": [],
"store": [],
"shopping_item": [],
"shopping_item_meal": [],
"shopping_item_recipe": [],
"store_aisle": [],
"store_product_aisle": [],
"product_group": [],
"product_group_member": [],
"store_product_group": [],
"store_product_override": [],
"shopping_trip": [],
"shopping_trip_item": [],
"shopping_trip_email": []
},
"tombstones": [
{
"table_name": "family_note",
"row_id": "fnt_01M6V9CS7WTZRFRHFAN0EGN39W"
}
]
}Pull as a device that is up to date
A device sends its client_id and the highest sequence number it has seen, and also learns what became of the commands it pushed.
curl 'https://rigel.house/api/v1/households/hh_01M6V9CS6GGVFCY7NAS8ZTQV95/pull?from=13&client_id=cli_01M6PHONE7R3QWT8XK2VNB0Y5D&after_seq=0' \
-H "Cookie: rigel_session=$RIGEL_SESSION"const response = await fetch("https://rigel.house/api/v1/households/hh_01M6V9CS6GGVFCY7NAS8ZTQV95/pull?from=13&client_id=cli_01M6PHONE7R3QWT8XK2VNB0Y5D&after_seq=0", {
headers: {
Cookie: `rigel_session=${process.env.RIGEL_SESSION}`,
},
});
const data = await response.json();GET /api/v1/households/hh_01M6V9CS6GGVFCY7NAS8ZTQV95/pull?from=13&client_id=cli_01M6PHONE7R3QWT8XK2VNB0Y5D&after_seq=0 HTTP/1.1
Host: rigel.house
Cookie: rigel_session=<your session>HTTP/1.1 200 OK
Content-Type: application/json
X-Request-Id: req_01M6V9CS85YTY6GR9M0YHTT1KN
{
"from": 13,
"version": 13,
"rows": {
"file": [],
"person": [],
"family_note": [],
"integration": [],
"notification": [],
"notification_mute": [],
"folder": [],
"document": [],
"document_body": [],
"document_extract": [],
"document_version": [],
"document_link": [],
"document_import": [],
"storage_location": [],
"product_category": [],
"product": [],
"recipe": [],
"recipe_ingredient": [],
"recipe_step": [],
"recipe_step_ingredient": [],
"inventory_item": [],
"dashboard_card": [],
"app_picture": [],
"agent_routine": [],
"task": [],
"task_dependency": [],
"tag": [],
"task_tag": [],
"task_comment": [],
"task_comment_mention": [],
"task_comment_reaction": [],
"task_watch": [],
"task_link": [],
"task_view": [],
"task_view_value": [],
"task_view_tag": [],
"agent_trigger": [],
"dish": [],
"dish_ingredient": [],
"meal": [],
"meal_person": [],
"meal_dish": [],
"dish_combo_icon": [],
"dish_combo_icon_dish": [],
"meals_setting": [],
"email_domain": [],
"email_address": [],
"email_domain_record": [],
"email_correspondent": [],
"email_thread": [],
"email_connection": [],
"email_connection_rule": [],
"email_message": [],
"email_message_recipient": [],
"email_attachment": [],
"email_action": [],
"email_draft": [],
"email_draft_recipient": [],
"email_draft_attachment": [],
"recipe_cook": [],
"calendar": [],
"calendar_event": [],
"calendar_event_exception": [],
"calendar_event_date": [],
"calendar_event_attachment": [],
"calendar_event_person": [],
"calendar_event_reminder": [],
"google_calendar": [],
"google_calendar_link": [],
"conversation": [],
"custom_emoji": [],
"sticker": [],
"message": [],
"message_attachment": [],
"message_mention": [],
"message_link": [],
"message_reaction": [],
"expression_pack": [],
"expression_pack_item": [],
"conversation_read": [],
"conversation_follow": [],
"sample_record": [],
"property": [],
"maintenance_location": [],
"category": [],
"model": [],
"evidence": [],
"asset": [],
"asset_photo": [],
"attribute": [],
"identification": [],
"identification_alternative": [],
"part": [],
"model_part": [],
"asset_part": [],
"warranty": [],
"contractor": [],
"contractor_contact": [],
"maintenance_task": [],
"maintenance_task_part": [],
"maintenance_project": [],
"maintenance_project_asset": [],
"maintenance_project_update": [],
"issue": [],
"visit": [],
"visit_asset": [],
"visit_issue": [],
"maintenance_task_completion": [],
"invoice": [],
"invoice_line": [],
"ongoing_service": [],
"store": [],
"shopping_item": [],
"shopping_item_meal": [],
"shopping_item_recipe": [],
"store_aisle": [],
"store_product_aisle": [],
"product_group": [],
"product_group_member": [],
"store_product_group": [],
"store_product_override": [],
"shopping_trip": [],
"shopping_trip_item": [],
"shopping_trip_email": []
},
"tombstones": [],
"last_client_seq": 0,
"outcomes": [],
"schema_version": 34
}Pull what happened to a device's commands
outcomes lists each command after after_seq as applied or rejected, with the reason.
curl 'https://rigel.house/api/v1/households/hh_01M6V9CS6GGVFCY7NAS8ZTQV95/pull?from=13&client_id=cli_01M6PHONE7R3QWT8XK2VNB0Y5D&after_seq=0' \
-H "Cookie: rigel_session=$RIGEL_SESSION"const response = await fetch("https://rigel.house/api/v1/households/hh_01M6V9CS6GGVFCY7NAS8ZTQV95/pull?from=13&client_id=cli_01M6PHONE7R3QWT8XK2VNB0Y5D&after_seq=0", {
headers: {
Cookie: `rigel_session=${process.env.RIGEL_SESSION}`,
},
});
const data = await response.json();GET /api/v1/households/hh_01M6V9CS6GGVFCY7NAS8ZTQV95/pull?from=13&client_id=cli_01M6PHONE7R3QWT8XK2VNB0Y5D&after_seq=0 HTTP/1.1
Host: rigel.house
Cookie: rigel_session=<your session>HTTP/1.1 200 OK
Content-Type: application/json
X-Request-Id: req_01M6V9CS8DF6P84N5ANDG5X6PF
{
"from": 13,
"version": 15,
"rows": {
"file": [],
"person": [],
"family_note": [
{
"id": "fnt_01M6V9CS85J9R3PRDTEYTGSZXV",
"created_at": 1794064492005,
"updated_at": 1794064492005,
"version": 14,
"person_id": "per_01M6V9CS79TMYHDAM7FTA9ZK3Z",
"text": "Nia is practicing her recorder for the winter concert.",
"app": null,
"noted_at": 1794064492005
},
{
"id": "fnt_01M6V9CS85VEP4QJ62EEW4F7EB",
"created_at": 1794064612005,
"updated_at": 1794064612005,
"version": 15,
"person_id": null,
"text": "The concert is the evening of Dec 10.",
"app": "calendar",
"noted_at": 1794064612005
}
],
"integration": [],
"notification": [],
"notification_mute": [],
"folder": [],
"document": [],
"document_body": [],
"document_extract": [],
"document_version": [],
"document_link": [],
"document_import": [],
"storage_location": [],
"product_category": [],
"product": [],
"recipe": [],
"recipe_ingredient": [],
"recipe_step": [],
"recipe_step_ingredient": [],
"inventory_item": [],
"dashboard_card": [],
"app_picture": [],
"agent_routine": [],
"task": [],
"task_dependency": [],
"tag": [],
"task_tag": [],
"task_comment": [],
"task_comment_mention": [],
"task_comment_reaction": [],
"task_watch": [],
"task_link": [],
"task_view": [],
"task_view_value": [],
"task_view_tag": [],
"agent_trigger": [],
"dish": [],
"dish_ingredient": [],
"meal": [],
"meal_person": [],
"meal_dish": [],
"dish_combo_icon": [],
"dish_combo_icon_dish": [],
"meals_setting": [],
"email_domain": [],
"email_address": [],
"email_domain_record": [],
"email_correspondent": [],
"email_thread": [],
"email_connection": [],
"email_connection_rule": [],
"email_message": [],
"email_message_recipient": [],
"email_attachment": [],
"email_action": [],
"email_draft": [],
"email_draft_recipient": [],
"email_draft_attachment": [],
"recipe_cook": [],
"calendar": [],
"calendar_event": [],
"calendar_event_exception": [],
"calendar_event_date": [],
"calendar_event_attachment": [],
"calendar_event_person": [],
"calendar_event_reminder": [],
"google_calendar": [],
"google_calendar_link": [],
"conversation": [],
"custom_emoji": [],
"sticker": [],
"message": [],
"message_attachment": [],
"message_mention": [],
"message_link": [],
"message_reaction": [],
"expression_pack": [],
"expression_pack_item": [],
"conversation_read": [],
"conversation_follow": [],
"sample_record": [],
"property": [],
"maintenance_location": [],
"category": [],
"model": [],
"evidence": [],
"asset": [],
"asset_photo": [],
"attribute": [],
"identification": [],
"identification_alternative": [],
"part": [],
"model_part": [],
"asset_part": [],
"warranty": [],
"contractor": [],
"contractor_contact": [],
"maintenance_task": [],
"maintenance_task_part": [],
"maintenance_project": [],
"maintenance_project_asset": [],
"maintenance_project_update": [],
"issue": [],
"visit": [],
"visit_asset": [],
"visit_issue": [],
"maintenance_task_completion": [],
"invoice": [],
"invoice_line": [],
"ongoing_service": [],
"store": [],
"shopping_item": [],
"shopping_item_meal": [],
"shopping_item_recipe": [],
"store_aisle": [],
"store_product_aisle": [],
"product_group": [],
"product_group_member": [],
"store_product_group": [],
"store_product_override": [],
"shopping_trip": [],
"shopping_trip_item": [],
"shopping_trip_email": []
},
"tombstones": [],
"last_client_seq": 3,
"outcomes": [
{
"client_seq": 1,
"command_id": "cmd_01M6V9CS85QPQN4JDKVH308QYZ",
"status": "applied",
"error": null
},
{
"client_seq": 2,
"command_id": "cmd_01M6V9CS85HPG7JWHT24KJWDZQ",
"status": "rejected",
"error": {
"code": "note_not_found",
"message": "That note isn't there any more."
}
},
{
"client_seq": 3,
"command_id": "cmd_01M6V9CS85HMC5XPRT3NDRR6QV",
"status": "applied",
"error": null
}
],
"schema_version": 34
}