Skip to content
FolioSign inStart your trial

API reference

Booth library

Sync the booth's songs, themes and service plans with the cloud.

GET/v1/device/library/changes

Pull library changes#

Returns objects changed since a library version. Pull with since=<next_since> until more is false, and store the last next_since. Deletes appear as tombstones with deleted: true. Narrow with kinds.

Auth. A booth device key. Needs the device:read scope. Send X-Folio-Device-Contract; an unsupported version gets 426.

Limits. Responses carry RateLimit-Limit and RateLimit-Remaining; a 429 carries Retry-After.

Auth
Booth device key
Scope
device:read
Operation id
deviceLibraryChanges

Parameters

Parameters
NameInDescription
sincequeryReturn what changed after this value. Start at 0 and pass the last next_since you received. (integer, e.g. 400)
kindsqueryOnly these kinds of object: song, theme, service_plan or service_record. Repeat the parameter for several. (string)
limitqueryHow many items to return, from 1 to 100. (integer, e.g. 100)

Responses

Response data fields
FieldTypeDescription
morebooleanWhether another page of changes exists.
next_sinceinteger (int64)Pass as since on the next pull.
objectstringThe object type.
objectsobject[]The library objects.
200 Success. Pull library changes.
200 example
{
  "status": true,
  "message": "library_changes",
  "data": {
    "object": "library_changes",
    "objects": [
      {
        "object": "library_object",
        "kind": "song",
        "id": "song-amazing-grace",
        "version": 412,
        "revision": 3,
        "hash": "9b74c9897bac770ffc029102a200c5de",
        "title": "Amazing Grace",
        "deleted": false,
        "updated_at": "2026-11-01T08:00:00Z",
        "updated_by": "usr_0192f7a8-3b1c-7d2e-9a40-5c6d7e8f9012",
        "updated_by_kind": "user",
        "body": {
          "title": "Amazing Grace",
          "verses": [
            "Amazing grace, how sweet the sound"
          ]
        }
      }
    ],
    "next_since": 412,
    "more": false
  }
}
400 The request could not be read.
400 example
{
  "status": false,
  "message": "the page cursor is not valid",
  "error": {
    "code": "invalid_argument",
    "status": 400,
    "request_id": "req_b337770ecebe03b03a88a8998b59263d"
  }
}
401 The key or session is missing, invalid or expired.
401 example
{
  "status": false,
  "message": "That API key is not valid or has expired.",
  "error": {
    "code": "unauthenticated",
    "status": 401,
    "request_id": "req_b337770ecebe03b03a88a8998b59263d"
  }
}
403 The credential lacks the scope, the church is suspended, the feature is not in the plan, or the address is not allowed.
403 example
{
  "status": false,
  "message": "That API key does not have the required scope.",
  "error": {
    "code": "permission_denied",
    "status": 403,
    "request_id": "req_b337770ecebe03b03a88a8998b59263d"
  }
}
404 The resource is not in this church.
404 example
{
  "status": false,
  "message": "That item is not in this church.",
  "error": {
    "code": "not_found",
    "status": 404,
    "request_id": "req_b337770ecebe03b03a88a8998b59263d"
  }
}
409 Conflict, including a duplicate Idempotency-Key still being processed.
409 example
{
  "status": false,
  "message": "A request with this Idempotency-Key is still running.",
  "error": {
    "code": "conflict",
    "status": 409,
    "request_id": "req_b337770ecebe03b03a88a8998b59263d"
  }
}
422 A field failed validation, or an Idempotency-Key was reused with a different body.
422 example
{
  "status": false,
  "message": "A field is not valid.",
  "error": {
    "code": "validation",
    "status": 422,
    "request_id": "req_b337770ecebe03b03a88a8998b59263d"
  }
}
426 The booth's contract version is no longer supported. Call GET /v1/device/update.
426 example
{
  "status": false,
  "message": "This booth must update Folio Live.",
  "error": {
    "code": "failed_precondition",
    "status": 426,
    "request_id": "req_b337770ecebe03b03a88a8998b59263d"
  }
}
429 Rate limited. Wait Retry-After seconds.
429 example
{
  "status": false,
  "message": "Too many requests. Wait before trying again.",
  "error": {
    "code": "rate_limited",
    "status": 429,
    "request_id": "req_b337770ecebe03b03a88a8998b59263d"
  }
}
500 Something went wrong. Quote error.request_id.
500 example
{
  "status": false,
  "message": "Something went wrong. Quote the request id to support.",
  "error": {
    "code": "internal",
    "status": 500,
    "request_id": "req_b337770ecebe03b03a88a8998b59263d"
  }
}

Code samples

curl -X GET "$FOLIO_API_URL/v1/device/library/changes?limit=100&since=400" \
  -H "Authorization: Bearer $FOLIO_DEVICE_KEY" \
  -H "X-Folio-Device-Contract: 1"

Try it is available for church operations and public reads. Booth devices pair with a code, so this operation is documented but not runnable here.

POST/v1/device/library/objects

Push a batch of library objects#

Writes up to 100 objects. Each carries the base_revision you last saw. If someone changed the object meanwhile, its result is a conflict that includes the current object; merge and retry that object. Results come back per object so one conflict does not fail the batch.

Auth. A booth device key. Needs the device:write scope. Send X-Folio-Device-Contract; an unsupported version gets 426.

Limits. Responses carry RateLimit-Limit and RateLimit-Remaining; a 429 carries Retry-After.

Auth
Booth device key
Scope
device:write
Operation id
deviceLibraryPush

Parameters

Parameters
NameInDescription
Idempotency-KeyheaderMakes a retry safe. The same key with the same body replays the first response for 24 hours; the same key with a different body returns 422; a duplicate that is still running returns 409 with Retry-After. Responses that carry a one-time secret are not replayed. (string, e.g. sunday-2026-11-01-upload)

Request body

Request body fields
FieldTypeDescription
objectsobject[]The library objects.
Example request body
{
  "objects": [
    {
      "kind": "song",
      "id": "song-amazing-grace",
      "base_revision": 3,
      "checksum": "9b74c9897bac770ffc029102a200c5de",
      "body": {
        "title": "Amazing Grace",
        "verses": [
          "Amazing grace, how sweet the sound"
        ]
      }
    }
  ]
}

Responses

Response data fields
FieldTypeDescription
objectstringThe object type.
resultsobject[]One result per object written.
200 Success. Push a batch of library objects.
200 example
{
  "status": true,
  "message": "library_push",
  "data": {
    "object": "library_push",
    "results": [
      {
        "kind": "song",
        "id": "song-amazing-grace",
        "outcome": "applied",
        "object": {
          "object": "library_object",
          "kind": "song",
          "id": "song-amazing-grace",
          "version": 412,
          "revision": 3,
          "hash": "9b74c9897bac770ffc029102a200c5de",
          "title": "Amazing Grace",
          "deleted": false,
          "updated_at": "2026-11-01T08:00:00Z",
          "updated_by": "usr_0192f7a8-3b1c-7d2e-9a40-5c6d7e8f9012",
          "updated_by_kind": "user",
          "body": {
            "title": "Amazing Grace",
            "verses": [
              "Amazing grace, how sweet the sound"
            ]
          }
        }
      }
    ]
  }
}
400 The request could not be read.
400 example
{
  "status": false,
  "message": "the page cursor is not valid",
  "error": {
    "code": "invalid_argument",
    "status": 400,
    "request_id": "req_b337770ecebe03b03a88a8998b59263d"
  }
}
401 The key or session is missing, invalid or expired.
401 example
{
  "status": false,
  "message": "That API key is not valid or has expired.",
  "error": {
    "code": "unauthenticated",
    "status": 401,
    "request_id": "req_b337770ecebe03b03a88a8998b59263d"
  }
}
403 The credential lacks the scope, the church is suspended, the feature is not in the plan, or the address is not allowed.
403 example
{
  "status": false,
  "message": "That API key does not have the required scope.",
  "error": {
    "code": "permission_denied",
    "status": 403,
    "request_id": "req_b337770ecebe03b03a88a8998b59263d"
  }
}
404 The resource is not in this church.
404 example
{
  "status": false,
  "message": "That item is not in this church.",
  "error": {
    "code": "not_found",
    "status": 404,
    "request_id": "req_b337770ecebe03b03a88a8998b59263d"
  }
}
409 Conflict, including a duplicate Idempotency-Key still being processed.
409 example
{
  "status": false,
  "message": "A request with this Idempotency-Key is still running.",
  "error": {
    "code": "conflict",
    "status": 409,
    "request_id": "req_b337770ecebe03b03a88a8998b59263d"
  }
}
422 A field failed validation, or an Idempotency-Key was reused with a different body.
422 example
{
  "status": false,
  "message": "A field is not valid.",
  "error": {
    "code": "validation",
    "status": 422,
    "request_id": "req_b337770ecebe03b03a88a8998b59263d"
  }
}
426 The booth's contract version is no longer supported. Call GET /v1/device/update.
426 example
{
  "status": false,
  "message": "This booth must update Folio Live.",
  "error": {
    "code": "failed_precondition",
    "status": 426,
    "request_id": "req_b337770ecebe03b03a88a8998b59263d"
  }
}
429 Rate limited. Wait Retry-After seconds.
429 example
{
  "status": false,
  "message": "Too many requests. Wait before trying again.",
  "error": {
    "code": "rate_limited",
    "status": 429,
    "request_id": "req_b337770ecebe03b03a88a8998b59263d"
  }
}
500 Something went wrong. Quote error.request_id.
500 example
{
  "status": false,
  "message": "Something went wrong. Quote the request id to support.",
  "error": {
    "code": "internal",
    "status": 500,
    "request_id": "req_b337770ecebe03b03a88a8998b59263d"
  }
}

Code samples

curl -X POST "$FOLIO_API_URL/v1/device/library/objects" \
  -H "Authorization: Bearer $FOLIO_DEVICE_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: push-2026-11-01-1" \
  -H "X-Folio-Device-Contract: 1" \
  -d '{
  "objects": [
    {
      "kind": "song",
      "id": "song-amazing-grace",
      "base_revision": 3,
      "checksum": "9b74c9897bac770ffc029102a200c5de",
      "body": {
        "title": "Amazing Grace",
        "verses": [
          "Amazing grace, how sweet the sound"
        ]
      }
    }
  ]
}'

Try it is available for church operations and public reads. Booth devices pair with a code, so this operation is documented but not runnable here.

GET/v1/device/library/objects/{kind}/{id}

Read one library object#

Returns one object with its body, current revision and hash.

Auth. A booth device key. Needs the device:read scope. Send X-Folio-Device-Contract; an unsupported version gets 426.

Limits. Responses carry RateLimit-Limit and RateLimit-Remaining; a 429 carries Retry-After.

Auth
Booth device key
Scope
device:read
Operation id
deviceLibraryGet

Parameters

Parameters
NameInDescription
kind requiredpathThe object kind: song, theme, service_plan or service_record. (string, e.g. song)
id requiredpathThe object id. (string, e.g. song-amazing-grace)

Responses

Response data fields
FieldTypeDescription
bodyvalueThe object's content as JSON.
deletedbooleanWhether it was deleted.
hashstringA hash of the content, for change detection.
idstringThe id, with a type prefix such as tch_ or note_.
kindstringThe kind.
objectstringThe object type.
revisioninteger (int32)The object's revision. Send it as base_revision when you write.
titlestringThe title.
updated_atstringWhen it last changed, in RFC 3339 UTC.
updated_bystringThe id of whoever last changed it.
updated_by_kindstringuser or device.
versioninteger (int64)The version.
200 Success. Read one library object.
200 example
{
  "status": true,
  "message": "library_object",
  "data": {
    "object": "library_object",
    "kind": "song",
    "id": "song-amazing-grace",
    "version": 412,
    "revision": 3,
    "hash": "9b74c9897bac770ffc029102a200c5de",
    "title": "Amazing Grace",
    "deleted": false,
    "updated_at": "2026-11-01T08:00:00Z",
    "updated_by": "usr_0192f7a8-3b1c-7d2e-9a40-5c6d7e8f9012",
    "updated_by_kind": "user",
    "body": {
      "title": "Amazing Grace",
      "verses": [
        "Amazing grace, how sweet the sound"
      ]
    }
  }
}
400 The request could not be read.
400 example
{
  "status": false,
  "message": "the page cursor is not valid",
  "error": {
    "code": "invalid_argument",
    "status": 400,
    "request_id": "req_b337770ecebe03b03a88a8998b59263d"
  }
}
401 The key or session is missing, invalid or expired.
401 example
{
  "status": false,
  "message": "That API key is not valid or has expired.",
  "error": {
    "code": "unauthenticated",
    "status": 401,
    "request_id": "req_b337770ecebe03b03a88a8998b59263d"
  }
}
403 The credential lacks the scope, the church is suspended, the feature is not in the plan, or the address is not allowed.
403 example
{
  "status": false,
  "message": "That API key does not have the required scope.",
  "error": {
    "code": "permission_denied",
    "status": 403,
    "request_id": "req_b337770ecebe03b03a88a8998b59263d"
  }
}
404 The resource is not in this church.
404 example
{
  "status": false,
  "message": "That item is not in this church.",
  "error": {
    "code": "not_found",
    "status": 404,
    "request_id": "req_b337770ecebe03b03a88a8998b59263d"
  }
}
409 Conflict, including a duplicate Idempotency-Key still being processed.
409 example
{
  "status": false,
  "message": "A request with this Idempotency-Key is still running.",
  "error": {
    "code": "conflict",
    "status": 409,
    "request_id": "req_b337770ecebe03b03a88a8998b59263d"
  }
}
422 A field failed validation, or an Idempotency-Key was reused with a different body.
422 example
{
  "status": false,
  "message": "A field is not valid.",
  "error": {
    "code": "validation",
    "status": 422,
    "request_id": "req_b337770ecebe03b03a88a8998b59263d"
  }
}
426 The booth's contract version is no longer supported. Call GET /v1/device/update.
426 example
{
  "status": false,
  "message": "This booth must update Folio Live.",
  "error": {
    "code": "failed_precondition",
    "status": 426,
    "request_id": "req_b337770ecebe03b03a88a8998b59263d"
  }
}
429 Rate limited. Wait Retry-After seconds.
429 example
{
  "status": false,
  "message": "Too many requests. Wait before trying again.",
  "error": {
    "code": "rate_limited",
    "status": 429,
    "request_id": "req_b337770ecebe03b03a88a8998b59263d"
  }
}
500 Something went wrong. Quote error.request_id.
500 example
{
  "status": false,
  "message": "Something went wrong. Quote the request id to support.",
  "error": {
    "code": "internal",
    "status": 500,
    "request_id": "req_b337770ecebe03b03a88a8998b59263d"
  }
}

Code samples

curl -X GET "$FOLIO_API_URL/v1/device/library/objects/song/song-amazing-grace" \
  -H "Authorization: Bearer $FOLIO_DEVICE_KEY" \
  -H "X-Folio-Device-Contract: 1"

Try it is available for church operations and public reads. Booth devices pair with a code, so this operation is documented but not runnable here.

PUT/v1/device/library/objects/{kind}/{id}

Write one library object#

Creates or replaces one object. base_revision is the revision you last read (0 to create). A stale revision answers a conflict with the current object so you can merge.

Auth. A booth device key. Needs the device:write scope. Send X-Folio-Device-Contract; an unsupported version gets 426.

Limits. Responses carry RateLimit-Limit and RateLimit-Remaining; a 429 carries Retry-After.

Auth
Booth device key
Scope
device:write
Operation id
deviceLibraryPut

Parameters

Parameters
NameInDescription
kind requiredpathThe object kind: song, theme, service_plan or service_record. (string, e.g. song)
id requiredpathThe object id. (string, e.g. song-amazing-grace)
Idempotency-KeyheaderMakes a retry safe. The same key with the same body replays the first response for 24 hours; the same key with a different body returns 422; a duplicate that is still running returns 409 with Retry-After. Responses that carry a one-time secret are not replayed. (string, e.g. sunday-2026-11-01-upload)

Request body

Request body fields
FieldTypeDescription
base_revisioninteger (int32)The revision you last read. 0 creates.
bodyvalueThe object's content.
checksumstringA checksum of the body you are sending.
Example request body
{
  "base_revision": 3,
  "checksum": "9b74c9897bac770ffc029102a200c5de",
  "body": {
    "title": "Amazing Grace",
    "verses": [
      "Amazing grace, how sweet the sound"
    ]
  }
}

Responses

200 Success. Write one library object.
200 example
{
  "status": true,
  "message": "library_object",
  "data": {
    "object": "library_object",
    "kind": "song",
    "id": "song-amazing-grace",
    "version": 412,
    "revision": 3,
    "hash": "9b74c9897bac770ffc029102a200c5de",
    "title": "Amazing Grace",
    "deleted": false,
    "updated_at": "2026-11-01T08:00:00Z",
    "updated_by": "usr_0192f7a8-3b1c-7d2e-9a40-5c6d7e8f9012",
    "updated_by_kind": "user",
    "body": {
      "title": "Amazing Grace",
      "verses": [
        "Amazing grace, how sweet the sound"
      ]
    }
  }
}
400 The request could not be read.
400 example
{
  "status": false,
  "message": "the page cursor is not valid",
  "error": {
    "code": "invalid_argument",
    "status": 400,
    "request_id": "req_b337770ecebe03b03a88a8998b59263d"
  }
}
401 The key or session is missing, invalid or expired.
401 example
{
  "status": false,
  "message": "That API key is not valid or has expired.",
  "error": {
    "code": "unauthenticated",
    "status": 401,
    "request_id": "req_b337770ecebe03b03a88a8998b59263d"
  }
}
403 The credential lacks the scope, the church is suspended, the feature is not in the plan, or the address is not allowed.
403 example
{
  "status": false,
  "message": "That API key does not have the required scope.",
  "error": {
    "code": "permission_denied",
    "status": 403,
    "request_id": "req_b337770ecebe03b03a88a8998b59263d"
  }
}
404 The resource is not in this church.
404 example
{
  "status": false,
  "message": "That item is not in this church.",
  "error": {
    "code": "not_found",
    "status": 404,
    "request_id": "req_b337770ecebe03b03a88a8998b59263d"
  }
}
409 Conflict, including a duplicate Idempotency-Key still being processed.
409 example
{
  "status": false,
  "message": "A request with this Idempotency-Key is still running.",
  "error": {
    "code": "conflict",
    "status": 409,
    "request_id": "req_b337770ecebe03b03a88a8998b59263d"
  }
}
422 A field failed validation, or an Idempotency-Key was reused with a different body.
422 example
{
  "status": false,
  "message": "A field is not valid.",
  "error": {
    "code": "validation",
    "status": 422,
    "request_id": "req_b337770ecebe03b03a88a8998b59263d"
  }
}
426 The booth's contract version is no longer supported. Call GET /v1/device/update.
426 example
{
  "status": false,
  "message": "This booth must update Folio Live.",
  "error": {
    "code": "failed_precondition",
    "status": 426,
    "request_id": "req_b337770ecebe03b03a88a8998b59263d"
  }
}
429 Rate limited. Wait Retry-After seconds.
429 example
{
  "status": false,
  "message": "Too many requests. Wait before trying again.",
  "error": {
    "code": "rate_limited",
    "status": 429,
    "request_id": "req_b337770ecebe03b03a88a8998b59263d"
  }
}
500 Something went wrong. Quote error.request_id.
500 example
{
  "status": false,
  "message": "Something went wrong. Quote the request id to support.",
  "error": {
    "code": "internal",
    "status": 500,
    "request_id": "req_b337770ecebe03b03a88a8998b59263d"
  }
}

Code samples

curl -X PUT "$FOLIO_API_URL/v1/device/library/objects/song/song-amazing-grace" \
  -H "Authorization: Bearer $FOLIO_DEVICE_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: put-song-amazing-grace-4" \
  -H "X-Folio-Device-Contract: 1" \
  -d '{
  "base_revision": 3,
  "checksum": "9b74c9897bac770ffc029102a200c5de",
  "body": {
    "title": "Amazing Grace",
    "verses": [
      "Amazing grace, how sweet the sound"
    ]
  }
}'

Try it is available for church operations and public reads. Booth devices pair with a code, so this operation is documented but not runnable here.

DELETE/v1/device/library/objects/{kind}/{id}

Delete one library object#

Marks the object deleted by writing a tombstone, so other booths learn of the delete on their next pull. Pass base_revision to avoid deleting a change you have not seen.

Auth. A booth device key. Needs the device:write scope. Send X-Folio-Device-Contract; an unsupported version gets 426.

Limits. Responses carry RateLimit-Limit and RateLimit-Remaining; a 429 carries Retry-After.

Auth
Booth device key
Scope
device:write
Operation id
deviceLibraryDelete

Parameters

Parameters
NameInDescription
kind requiredpathThe object kind: song, theme, service_plan or service_record. (string, e.g. song)
id requiredpathThe object id. (string, e.g. song-amazing-grace)
base_revisionqueryThe revision you last read. A stale value is refused so you do not delete a change you have not seen. (integer, e.g. 4)
Idempotency-KeyheaderMakes a retry safe. The same key with the same body replays the first response for 24 hours; the same key with a different body returns 422; a duplicate that is still running returns 409 with Retry-After. Responses that carry a one-time secret are not replayed. (string, e.g. sunday-2026-11-01-upload)

Responses

200 Success. Delete one library object.
200 example
{
  "status": true,
  "message": "library_object",
  "data": {
    "object": "library_object",
    "kind": "song",
    "id": "song-amazing-grace",
    "deleted": true
  }
}
400 The request could not be read.
400 example
{
  "status": false,
  "message": "the page cursor is not valid",
  "error": {
    "code": "invalid_argument",
    "status": 400,
    "request_id": "req_b337770ecebe03b03a88a8998b59263d"
  }
}
401 The key or session is missing, invalid or expired.
401 example
{
  "status": false,
  "message": "That API key is not valid or has expired.",
  "error": {
    "code": "unauthenticated",
    "status": 401,
    "request_id": "req_b337770ecebe03b03a88a8998b59263d"
  }
}
403 The credential lacks the scope, the church is suspended, the feature is not in the plan, or the address is not allowed.
403 example
{
  "status": false,
  "message": "That API key does not have the required scope.",
  "error": {
    "code": "permission_denied",
    "status": 403,
    "request_id": "req_b337770ecebe03b03a88a8998b59263d"
  }
}
404 The resource is not in this church.
404 example
{
  "status": false,
  "message": "That item is not in this church.",
  "error": {
    "code": "not_found",
    "status": 404,
    "request_id": "req_b337770ecebe03b03a88a8998b59263d"
  }
}
409 Conflict, including a duplicate Idempotency-Key still being processed.
409 example
{
  "status": false,
  "message": "A request with this Idempotency-Key is still running.",
  "error": {
    "code": "conflict",
    "status": 409,
    "request_id": "req_b337770ecebe03b03a88a8998b59263d"
  }
}
422 A field failed validation, or an Idempotency-Key was reused with a different body.
422 example
{
  "status": false,
  "message": "A field is not valid.",
  "error": {
    "code": "validation",
    "status": 422,
    "request_id": "req_b337770ecebe03b03a88a8998b59263d"
  }
}
426 The booth's contract version is no longer supported. Call GET /v1/device/update.
426 example
{
  "status": false,
  "message": "This booth must update Folio Live.",
  "error": {
    "code": "failed_precondition",
    "status": 426,
    "request_id": "req_b337770ecebe03b03a88a8998b59263d"
  }
}
429 Rate limited. Wait Retry-After seconds.
429 example
{
  "status": false,
  "message": "Too many requests. Wait before trying again.",
  "error": {
    "code": "rate_limited",
    "status": 429,
    "request_id": "req_b337770ecebe03b03a88a8998b59263d"
  }
}
500 Something went wrong. Quote error.request_id.
500 example
{
  "status": false,
  "message": "Something went wrong. Quote the request id to support.",
  "error": {
    "code": "internal",
    "status": 500,
    "request_id": "req_b337770ecebe03b03a88a8998b59263d"
  }
}

Code samples

curl -X DELETE "$FOLIO_API_URL/v1/device/library/objects/song/song-amazing-grace?base_revision=4" \
  -H "Authorization: Bearer $FOLIO_DEVICE_KEY" \
  -H "X-Folio-Device-Contract: 1"

Try it is available for church operations and public reads. Booth devices pair with a code, so this operation is documented but not runnable here.

Verified against API version 0.0.0-dev. Generated from the live OpenAPI document.