gairė

API

Gairė's API lets a program of yours see your watches and hear about each change as soon as a check finds it. Gairė's Zapier app is built on it.

The API reads and listens. It does not add or change watches: in Gairė, a person sees what a watch will read and says yes before it starts.

Signing in

The API uses OAuth 2.0: the authorization code flow, with PKCE (S256).

  • Server details: https://gaire.lt/.well-known/oauth-authorization-server
  • Registering a client: https://gaire.lt/oauth/register (dynamic client registration). Clients are public: no client secret, PKCE instead.
  • Scopes: gaire:read to see watches and changes; gaire:write also to subscribe an address and to stop it.

The person signs in to Gairė and sees which program is asking, and for what, before saying yes. Send the token with each request:

Authorization: Bearer <access token>

An access token lasts 1 hour and a refresh token 90 days. Each refresh gives a new refresh token, good for another 90 days, so keep the new one. If an old refresh token is used more than a minute after it was replaced, the sign-in ends and the person signs in again. This stops a stolen token from being used.

Endpoints

All of them are under https://gaire.lt/api/v1 and answer in JSON.

GET /me

The signed-in account: account_id, email (masked, as m•••@example.com), client (the program's name) and watch_count.

GET /watches

Your watches. alert_rule is a watch's rule on a price or a word (only rows below or above a price, or whose name mentions a word, are told), or null.
{
  "watches": [
    { "id": "…", "name": "Laptops on shop.lt", "url": "https://shop.lt/laptops", "status": "active", "alert_rule": { "price_below": 800 } }
  ]
}

POST /hooks

Sends each change to an address of yours. Needs gaire:write.
{ "url": "https://example.com/gaire", "watch_id": "…" }

Leave out watch_id to hear about every watch on the account. The answer, 201, gives the subscription's id and its secret. Subscribing the same address and watch again gives back the same subscription.

{ "id": "…", "url": "https://example.com/gaire", "watch_id": "…", "secret": "…" }

DELETE /hooks/{id}

Stops sending to that address. Answers 204. Needs gaire:write.

GET /changes

The most recent changes, newest first, each in the same shape as a hook's request. ?watch_id=… narrows them to one watch, and ?limit= sets how many (3 if left out, at most 25). It is there for testing, to see what a hook will receive; here each change's url is your changes page on Gairė.

What a hook receives

One POST for each change, with the same JSON body a webhook gets: the five fields, then the watch, when the change was found, and every thing that changed with its old and new values. It is signed and delivered the way a webhook is: the same two headers, the same check, and an answer within 5 seconds. The body and each field are on the Webhooks page.

A hook differs from a webhook recipient in these ways:

  • It needs no six-digit code.
  • It does not take one of your 5 recipients.
  • It is sent at any hour, whatever your quiet hours.
  • It belongs to the program that made it: disconnect the program and its hooks stop.
  • Each account can have up to 20 of them. One is dropped when its address answers 410, or after 10 failed sends in a row.

Hooks count with webhooks: on the free plan, up to 300 a month are sent between them. As for every recipient, one watch sends to one address at most once in 30 minutes.

Errors

An error answers with a status and a JSON body:

{ "error": { "code": "cap", "message": "…" } }
  • 400: the address isn't https:// on the public internet (bad_url), the watch isn't yours (unknown_watch), or the request is malformed.
  • 401: there is no token, or it has expired. The WWW-Authenticate header says where to sign in.
  • 403: the token lacks the scope (insufficient_scope).
  • 404: there is no such watch, subscription or endpoint on this account.
  • 409: the account already has 20 subscriptions (cap).

Questions: maksim@gaire.lt