/ api/ v1/ account(getAccount)Plan and usage, the Free vs Plus plan table (limits, features, price), this token's access, remaining daily write/delete budgets and batch sizes. Call it first; reading it spends no budget.
No parameters.
Developers · REST API
Read and change a user's Owlect collections and items over HTTPS. Every request carries an OAuth access token the user granted to your app, and sees only that user's data.
Base URL
https://owlect.app/api/v1Register your app
POST your redirect URI to the registration endpoint and keep the client_id it returns.
Get a token
Send the user to the authorize URL, they sign in with Google and click Allow, then exchange the code for an access token.
Call the API
Send Authorization: Bearer <token> with every request. Start with GET /account.
Owlect is an OAuth 2.1 authorization server. Apps are public clients that prove themselves with PKCE; there is no client secret to leak.
Redirect URIs must be https, or http on localhost. Registration is open and rate limited.
curl -X POST https://owlect.app/api/oauth/register \
-H "Content-Type: application/json" \
-d '{"client_name": "My app", "redirect_uris": ["https://myapp.example/callback"]}'Use response_type=code, a PKCE S256 code_challenge, your redirect_uri, a state value and scope="read create update delete" (ask only for what you need). The user signs in with Google, sees a consent screen naming your redirect host, and ticks what to allow: read and create are pre-selected. The token response's scope says what was granted.
https://owlect.app/oauth/authorize?response_type=code
&client_id=CLIENT_ID
&redirect_uri=https%3A%2F%2Fmyapp.example%2Fcallback
&code_challenge=CODE_CHALLENGE&code_challenge_method=S256
&scope=read%20create%20update%20delete&state=STATEPOST the code and your code_verifier to the token endpoint. You get an access token (1 hour) and a refresh token (30 days), which rotates on every use: always store the newest one.
curl -X POST https://owlect.app/api/oauth/token \
-d grant_type=authorization_code \
-d code=CODE -d client_id=CLIENT_ID \
-d redirect_uri=https://myapp.example/callback \
-d code_verifier=CODE_VERIFIERSend Authorization: Bearer <access_token>. When it expires, use grant_type=refresh_token for a new pair.
curl -X POST https://owlect.app/api/oauth/token \
-d grant_type=refresh_token \
-d refresh_token=REFRESH_TOKEN -d client_id=CLIENT_IDreadSee your collections and items. Always on.
createAdd collections and items. On by default.
updateChange collections and items. Off unless you tick it.
deleteDelete collections and items. Off unless you tick it.
There are no personal API keys yet: every token comes from a user approving your app, and the user can revoke it at any time in Settings -> Connected apps.
All endpoints take and return JSON. Ids are UUIDs.
/ api/ v1/ account(getAccount)Plan and usage, the Free vs Plus plan table (limits, features, price), this token's access, remaining daily write/delete budgets and batch sizes. Call it first; reading it spends no budget.
No parameters.
/ api/ v1/ collections(listCollections)All of the user's collections, most recently updated first.
No parameters.
/ api/ v1/ collections(createCollection)Creates a collection. Built-in types get their standard fields unless fieldDefinitions is sent. The custom type needs Owlect Plus.
namebodyRequired
string, 1-100
Collection name.
typebodyRequired
dolls | board_games | coins | stamps | music | pokemon_cards | sneakers | retro_games | funko_pop | lego | comic_books | books | watches | cars | hot_wheels | custom
Collection type. Decides the default fields and the lookups used in the app.
descriptionbody
string, max 500
Shown on the collection page.
fieldDefinitionsbody
array of { key, label, type, options?, required? }
Custom fields. Omit to use the type's standard fields.
/ api/ v1/ collections/ {collectionId}(getCollection)One collection with its custom field definitions. Read these before writing customFieldValues.
collectionIdpathRequired
uuid
Id of one of the user's collections (from the collection list).
/ api/ v1/ collections/ {collectionId}(updateCollection)Changes the name, description or field definitions. Fields you do not send stay as they are.
collectionIdpathRequired
uuid
Collection to change.
namebody
string, 1-100
New name.
descriptionbody
string, max 500
New description.
fieldDefinitionsbody
array of { key, label, type, options?, required? }
Replaces the whole field list.
/ api/ v1/ collections/ {collectionId}(deleteCollection)Deletes the collection and every item in it. confirmName must match the collection's name exactly.
collectionIdpathRequired
uuid
Collection to delete.
confirmNamequeryRequired
string (the collection's exact name)
Must equal the collection's name exactly.
/ api/ v1/ collections/ {collectionId}/ items(searchItems)Items in a collection, newest first, with an optional name search and for-sale filter. Paginated with a cursor.
collectionIdpathRequired
uuid
Collection to search.
queryquery
string, max 200
Case-insensitive match on the item name.
forSalequery
boolean
true for items listed for sale, false for the rest.
limitquery
integer 1-50, default 20
Items per page.
cursorquery
string (nextCursor from the previous page)
nextCursor from the previous page. Omit it for the first page.
/ api/ v1/ collections/ {collectionId}/ items(createItems)Adds up to 50 items in one request. Custom field values are validated first; if any item is invalid, nothing is saved.
collectionIdpathRequired
uuid
Collection to add to.
itemsbodyRequired
array, 1-50 items
The items to create.
items[].namebodyRequired
string, 1-200
Item name.
items[].descriptionbody
string, max 1000
Free-text notes.
items[].quantitybody
integer 1-999
How many copies you own.
items[].customFieldValuesbody
object: field key -> string | number | boolean | null
Values keyed by the collection's field keys (read the collection first to get them). Unknown keys are rejected with the list of valid ones.
items[].forSalebody
boolean
Lists the item for sale.
items[].salePricebody
integer (whole currency units) | null
Asking price in whole units, e.g. 40 for $40.
items[].saleCurrencybody
currency code, e.g. USD, EUR, UAH
Currency of the sale price. Defaults to USD.
items[].completenessbody
complete | incomplete | partial | sealed | unknown | null
Whether the item is complete, sealed and so on.
items[].barcodebody
string, max 64 | null
EAN, UPC or ISBN.
items[].coverUrlbody
https URL | null
Link to a cover image. Stored as a link, not uploaded.
/ api/ v1/ collections/ {collectionId}/ items(deleteItems)Deletes up to 25 items from the collection.
collectionIdpathRequired
uuid
Collection the items belong to.
idsqueryRequired
comma-separated uuids
Comma-separated ids of the items to delete.
/ api/ v1/ items/ {itemId}(getItem)One item with every field.
itemIdpathRequired
uuid
Id of an item (from listing or searching a collection).
/ api/ v1/ items/ {itemId}(updateItem)Changes only the fields you send. customFieldValues merge by key and null clears a field.
itemIdpathRequired
uuid
Item to change.
namebody
string, 1-200
New name.
descriptionbody
string, max 1000
Free-text notes.
quantitybody
integer 1-999
How many copies you own.
customFieldValuesbody
object: field key -> string | number | boolean | null
Values keyed by the collection's field keys (read the collection first to get them). Unknown keys are rejected with the list of valid ones.
forSalebody
boolean
Lists the item for sale.
salePricebody
integer (whole currency units) | null
Asking price in whole units, e.g. 40 for $40.
saleCurrencybody
currency code, e.g. USD, EUR, UAH
Currency of the sale price. Defaults to USD.
completenessbody
complete | incomplete | partial | sealed | unknown | null
Whether the item is complete, sealed and so on.
barcodebody
string, max 64 | null
EAN, UPC or ISBN.
coverUrlbody
https URL | null
Link to a cover image. Stored as a link, not uploaded.
Replace $TOKEN with an access token and the ids with real ones.
curl https://owlect.app/api/v1/collections \
-H "Authorization: Bearer $TOKEN"curl -X POST https://owlect.app/api/v1/collections/COLLECTION_ID/items \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"items": [
{"name": "Wingspan", "customFieldValues": {"game_type": "Base Game"}},
{"name": "Scythe", "quantity": 2}
]}'curl "https://owlect.app/api/v1/collections/COLLECTION_ID/items?forSale=true&limit=20" \
-H "Authorization: Bearer $TOKEN"curl -X PATCH https://owlect.app/api/v1/items/ITEM_ID \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"forSale": true, "salePrice": 40, "saleCurrency": "USD"}'Errors use one JSON shape and a meaningful HTTP status. `code` is stable; `message` is for people and may change.
{
"error": {
"code": "invalid_input",
"message": "Unknown field \"publsher\". This collection's fields are: ...",
"details": {
"validKeys": [
"publisher",
"players"
]
}
}
}GET /collections/{collectionId}/items returns { items, nextCursor }. Pass nextCursor back as ?cursor= for the next page; it is null on the last page. Pages are stable while you write.
Limits apply per connected app (grant). The REST API and the MCP server share them.
A Custom GPT can use the Owlect API as an Action.
OpenAPI spec
https://owlect.app/api/v1/openapi.jsonGPT Actions use a client secret, which Owlect issues per GPT on request. Email support@owlect.app with the GPT's callback URL.
This is v1. Adding endpoints, optional parameters or response fields is not a breaking change; anything that would break a client goes into a new version with notice.