Developers · REST API

Owlect 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/v1
v1JSONOAuth 2.1 + PKCEOpenAPI 3.111 endpointsOpenAPI spec →View as Markdown →

Quick start

  1. 1

    Register your app

    POST your redirect URI to the registration endpoint and keep the client_id it returns.

  2. 2

    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.

  3. 3

    Call the API

    Send Authorization: Bearer <token> with every request. Start with GET /account.

Authentication

Owlect is an OAuth 2.1 authorization server. Apps are public clients that prove themselves with PKCE; there is no client secret to leak.

1. Register a client

Redirect URIs must be https, or http on localhost. Registration is open and rate limited.

bash
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"]}'

2. Send the user to authorize

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.

url
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=STATE

3. Exchange the code

POST 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.

bash
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_VERIFIER

4. Call the API

Send Authorization: Bearer <access_token>. When it expires, use grant_type=refresh_token for a new pair.

bash
curl -X POST https://owlect.app/api/oauth/token \
  -d grant_type=refresh_token \
  -d refresh_token=REFRESH_TOKEN -d client_id=CLIENT_ID

Scopes

read

See your collections and items. Always on.

create

Add collections and items. On by default.

update

Change collections and items. Off unless you tick it.

delete

Delete 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.

Endpoints

All endpoints take and return JSON. Ids are UUIDs.

Account

GET/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.

Collections

GET/api/v1/collections(listCollections)

All of the user's collections, most recently updated first.

No parameters.

POST/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.

GET/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).

PATCH/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.

DELETE/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.

Items

GET/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.

POST/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.

DELETE/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.

GET/api/v1/items/{itemId}(getItem)

One item with every field.

  • itemIdpathRequired

    uuid

    Id of an item (from listing or searching a collection).

PATCH/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.

Examples

Replace $TOKEN with an access token and the ids with real ones.

List collections

bash
curl https://owlect.app/api/v1/collections \
  -H "Authorization: Bearer $TOKEN"

Add items to a collection

bash
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}
  ]}'

Search items that are for sale

bash
curl "https://owlect.app/api/v1/collections/COLLECTION_ID/items?forSale=true&limit=20" \
  -H "Authorization: Bearer $TOKEN"

List an item for sale

bash
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

Errors use one JSON shape and a meaningful HTTP status. `code` is stable; `message` is for people and may change.

json
{
  "error": {
    "code": "invalid_input",
    "message": "Unknown field \"publsher\". This collection's fields are: ...",
    "details": {
      "validKeys": [
        "publisher",
        "players"
      ]
    }
  }
}
401 unauthorized
No token, or it is invalid, expired or revoked. Refresh it or send the user through authorization again.
403 insufficient_scope
The token only has the read scope.
403 plan_limit
The user's plan limit was reached. details has the limit and an upgradeUrl.
404 not_found
No such collection or item for this user.
409 confirmation_required
confirmName did not match the collection's name.
413 payload_too_large
The request body is over 1 MB. Send fewer items per request.
422 invalid_input
The input failed validation, or the body had a key the endpoint does not know (details.unknownKeys). details.validKeys lists a collection's field keys when a custom field was unknown.
429 rate_limited
Too many requests. Wait for the Retry-After header.
503 unavailable
A temporary problem on Owlect's side. Retry shortly.
500 internal
An unexpected error. Retry; if it keeps happening, contact support.

Pagination

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

Limits apply per connected app (grant). The REST API and the MCP server share them.

  • 120 requests per minute.
  • 500 write requests per day, of which 20 may be deletes.
  • Per user, across all their connected apps: 1000 write requests and 40 deletes per day.
  • Up to 50 items per create request and 25 per delete request.
  • The user's plan limits for items and collections apply as in the app.

Custom GPTs (ChatGPT Actions)

A Custom GPT can use the Owlect API as an Action.

OpenAPI spec

https://owlect.app/api/v1/openapi.json
  1. 1In the GPT editor, open Actions -> Create new action -> Import from URL, and paste the OpenAPI spec URL.
  2. 2Under Authentication choose OAuth. Authorization URL: https://owlect.app/oauth/authorize. Token URL: https://owlect.app/api/oauth/token. Scope: read create update delete (the user still chooses on the consent screen). Token exchange method: Default (POST request).
  3. 3Enter the client ID and client secret Owlect gives you for the GPT, then copy the callback URL ChatGPT shows so it can be registered.

GPT Actions use a client secret, which Owlect issues per GPT on request. Email support@owlect.app with the GPT's callback URL.

Versioning

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.