Skip to the main content
PicksYou

The PicksYou API

Everything on this page is generated from the same contract our own tests hold the code against, so it cannot quietly stop being true. It is a server to server API: a key belongs on your server, never in anything a visitor can read.

What this is

What AI assistants say when somebody asks for a business like this one.

The paragraphs below are the contract's own preamble, printed word for word. They are the terms a client inherits by calling this API at all.

A client reads, for one business, how often assistants picked it out of how many answers, the questions and the answers word for word, what is standing in the way of being picked, the visits that arrived at the business's own site from an assistant, and the snippet that records them. It can start a check, and a distribution partner can provision a shop, put it on a plan, retire it and erase it.

THE LAW A CLIENT INHERITS. Every count in this API travels with the n it came out of, the interval around it and the sentence we would print ourselves, and a client prints all of it or none of it. There is no key anywhere in this contract for a place in a list, a grade or a share of anything, and inventing one is publishing a claim the data does not carry. docs/api/README.md carries the worked example and the words to avoid.

EVERY ANSWER, INCLUDING EVERY REFUSAL, carries Cache-Control no-store and an X-Request-Id (a ULID) to quote when you write to us. There are no exceptions: an address nothing answers and a verb an address does not take are refused by the router before the group runs, and the error renderer mints the id for them.

THIS API IS SERVER TO SERVER. No Access-Control header is ever sent, so a browser page cannot call it cross-origin, and a key belongs on your server rather than in anything a visitor can read.

Contract version: 1.0.0

Where to send it

Two addresses, each with its own database, its own keys and its own receipts. A key minted on one never answers on the other.

Where to send it
Base address Which one it is
https://picksyou.com/api/v1 Production.
https://staging.picksyou.com/api/v1 Staging. Its own database, its own keys, its own receipts.

The key, and how to send it

Every request carries one key in the Authorization header. There is no other way in: no cookie, no session, no query parameter, and no browser page can call this API cross origin because no Access-Control header is ever sent.

Authorization: Bearer

The key, and how to send it
Kind How it is sent What it is
customerKey http bearer A customer's own key, py_c_ and then 48 characters. The account holder mints it at /app/account/api, chooses whether it may start checks, and may scope it to one business; it is shown once and stored as a hash, so a lost key is revoked and replaced rather than recovered. Send it as Authorization: Bearer py_c_...
partnerKey http bearer A distribution partner's key, py_p_ and then 48 characters, issued by us in the admin panel and shown once. It carries all three abilities and it is the only kind of key the partner endpoints answer. Send it as Authorization: Bearer py_p_...

Make a key in your account

What a key may do

A key carries one ability and the read ability comes with all of them. An endpoint that asks for an ability a key does not carry is refused, and minting a new key is the only way to widen one.

What a key may do
Ability What it opens
read Every GET on this page: the business, the weeks, the window, the runs, the answers, the blockers, the names beside you, the receipts and the snippet. Every key carries it.
checks Starting a check, which spends real money with several vendors and is capped per day. A customer chooses it when they mint the key; every partner key carries it.
connections A distribution partner's own doors: provisioning a shop, putting it on a plan, retiring it and erasing it. Only we can issue a key that carries this.
pages The two doors a customer's own website opens for the pages that customer approved. Nothing reaches either list without an approval the customer wrote first.
partner_links The two doors a customer's own website opens for its partners page. Every pair on that page was agreed by both businesses, one click each.

The limits, and where each one bites

Counted per key and per address, in the buckets below. They are written on the groups rather than on each endpoint, because a bucket is shared by every door in its group.

key
Who this key is and what it may see. Rate limit: 120 requests a minute for a customer key and 600 for a partner key, counted on the KEY. On top of that, 600 requests a minute from one address, refusals included, and 60 refused requests a minute from one address in a bucket of their own. The address caps are generous because one shared host carries hundreds of sites; the per key cap is the one that bites.
reads
The panel's own numbers, read by a machine: the business, the weeks, the window cut by assistant or by question, the runs, the answers, the blockers, the names beside you, the receipts and the snippet. Every one of them is a GET and needs the read ability, which every key carries. Rate limit: the per key cap of the key tag.
checks
Starting a check. Needs the checks ability, which a customer chooses when they mint the key and every partner key carries. Rate limit: 20 a minute per key, over a day cap of 3 checks that is the one that decides. The day cap is counted PER KEY on a customer key and PER SHOP on a partner key, because one partner key serves every merchant that installed the app and the fourth shop to install on a given day must still get its first check. A check spends real money with five vendors, which is why the day cap is small; a repeat inside the dedupe window is answered with the run that already exists rather than a second one.
pages
The two doors a customer's own website opens for the pages that customer approved. Needs the pages ability, which a customer ticks when they mint the key on the WordPress page and which a partner key can never carry. Rate limit: 20 a minute per key, in a bucket of their own, so a site syncing its pages cannot shut its own checks door. The site PULLS: it fetches what its owner already approved and tells us what it did, and we hold no credential of the site and nothing of it but the id of the post it made. Nothing appears on either list without an approval the customer wrote first, and what actually writes the receipt is our own read of the published page, never the report.
partner links
The two doors a customer's own website opens for its partners page. Needs the partner_links ability, which a customer ticks when they mint the key on the WordPress page and which a partner key can never carry. Rate limit: 20 a minute per key, in a bucket of their own, so a site syncing its partners cannot shut its own pages or checks door. A partners page lists the businesses in other trades this business recommends and that recommend it back; every pair on it was agreed by both businesses, one click each, and each click wrote an immutable approvals row. The site PULLS the list on its own clock and prints what it last read, and the one thing it tells us about itself is which page shows the list.
partner
A distribution partner's own six doors. Needs a partner key with the connections ability, which only we can issue. Rate limit: 600 a minute per key, plus the two address caps.

When something is refused

One shape for every refusal, so a client writes one branch and reads the code. The message is English on every request and is for a person reading a log, never for a business owner reading a page.

The fields of an error body
Field Type What it means
error object
error.code string The closed list. Adding one is a change to this file as well as to the code, and a test holds the two together.
error.message string One English sentence. Never a trace and never a file path.
error.fields object On validation_failed only: the field name that could not be used, and what was wrong with it.
error.retry_after integer On rate_limited, checks_limit_reached, api_paused and plan_grants_paused: seconds to wait. The same number is in the Retry-After header.
error.limit integer On checks_limit_reached only: how many new checks a day this key may start (per key for a customer key, per shop for a partner key). A client prints this number; it never parses the message for it.
error.paused object On business_paused only (402). This business has an open invoice, so its dashboard and every read about it are paused until the sum is paid. Nothing has been removed. The facts are the ones the panel's own paused page states, so a plugin can print a sentence its owner can act on: the sum, what it is made of, and where it is settled.
error.paused.amount string The open sum, as a decimal string.
error.paused.currency string
error.paused.vat string How much of the sum is VAT, as a decimal string.
error.paused.invoice_count integer
error.paused.invoices array of object Every open invoice, oldest first, by number, issue date and gross. They are what the sum adds up to.
error.paused.paused_since string The day the dashboard shut, or null.
error.paused.plan_ends_on string The day the plan ends if the sum is still open then, or null.
error.paused.billing_url string The page that settles it.

The closed list of codes

Adding one is a change to the contract as well as to the code, and a test holds the two together.

  • unauthenticated
  • forbidden
  • not_found
  • method_not_allowed
  • validation_failed
  • rate_limited
  • checks_limit_reached
  • business_owned
  • business_connected
  • connection_not_active
  • api_paused
  • business_paused
  • plan_grants_paused
  • plan_required
  • body_too_large
  • server_error

What each refusal means

Unauthenticated
No live key on this request. A missing header, a scheme that is not Bearer, a key we have never seen, a revoked key, an expired key, a key on a closed account and a partner key whose partner is gone all answer this same body: telling them apart would tell somebody holding a stolen key whether it is still worth trying. Ask the person to paste the key again; do not retry the same one.
Forbidden
The key is live and it may not do this: it does not carry the ability the endpoint asks for, or it is the wrong kind of key for the endpoint. Retrying will not help. A customer whose key cannot start checks has to mint a new one.
NotFound
There is nothing here for this key. A business that does not exist and a business this key may not see are the same answer, deliberately: a different answer for the two would let a stranger walk the integers and learn how many customers we have.
MethodNotAllowed
That address does not answer this method. The router refuses this one before the route group runs, so the request id is minted by the error renderer instead of by the middleware; the header is there either way.
ValidationFailed
Something you sent could not be used, and error.fields names it. Nothing is ever silently clamped: limit=5000 is a refusal rather than a page of fifty, because a client that asked for five thousand and was handed fifty without being told has written a loop that never terminates.
BodyTooLarge
The request body is over 16 KB, refused in front of the controller.
RateLimited
Too many requests. retry_after is in the body and in the Retry-After header; wait that long, then try again. The cap that bites is per key.
ServerError
Something broke on our side. The body never carries a trace, a file path or an exception name, not even on a deployment left in debug mode. Quote the X-Request-Id when you write to us.
ApiPaused
The API is switched off right now, refused before anything is authenticated. retry_after is an hour. Back off and come back; do not treat it as a bad key and do not ask the person to re-enter anything.

Every endpoint

One section per address and verb, in the contract's own order. Every answer also carries a request id to quote if you write to us, and is never stored in a shared cache.

GET /me

What this key is and what it can see

The first call an integration makes. A key pasted into a settings field has to answer three questions before anything is drawn: did it work, whose is it, and which business is it for. A key minted for one site lists that one site, so a plugin installed on one site never has to show a chooser. Never in this body: the token, its hash, or the account's address.

What goes in the address

Nothing in the address.

What you send

Nothing to send.

What comes back

What comes back: GET /me
Answer Shape What it means
200 Me The key, its actor, and what it may read.
401 Error No live key on this request. A missing header, a scheme that is not Bearer, a key we have never seen, a revoked key, an expired key, a key on a closed account and a partner key whose partner is gone all answer this same body: telling them apart would tell somebody holding a stolen key whether it is still worth trying. Ask the person to paste the key again; do not retry the same one.
404 Error There is nothing here for this key. A business that does not exist and a business this key may not see are the same answer, deliberately: a different answer for the two would let a stranger walk the integers and learn how many customers we have.
405 Error That address does not answer this method. The router refuses this one before the route group runs, so the request id is minted by the error renderer instead of by the middleware; the header is there either way.
429 Error Too many requests. retry_after is in the body and in the Retry-After header; wait that long, then try again. The cap that bites is per key.
500 Error Something broke on our side. The body never carries a trace, a file path or an exception name, not even on a deployment left in debug mode. Quote the X-Request-Id when you write to us.
503 Error The API is switched off right now, refused before anything is authenticated. retry_after is an hour. Back off and come back; do not treat it as a bad key and do not ask the person to re-enter anything.

GET /businesses

Every business this key may read

A customer key lists the account's own businesses, narrowed to one when the key carries a scope. A partner key lists the shops it has a live connection to; an erased shop is gone from here, because a redaction is a merchant asking to be forgotten and a list that still named them would be the one place the name came back. Ordered by id, always. This list is not paginated: an account holds at most five businesses, and a partner with many shops walks /partner/connections, which has a cursor.

What goes in the address

Nothing in the address.

What you send

Nothing to send.

What comes back

What comes back: GET /businesses
Answer Shape What it means
200 BusinessList The businesses, in id order.
401 Error No live key on this request. A missing header, a scheme that is not Bearer, a key we have never seen, a revoked key, an expired key, a key on a closed account and a partner key whose partner is gone all answer this same body: telling them apart would tell somebody holding a stolen key whether it is still worth trying. Ask the person to paste the key again; do not retry the same one.
403 Error The key is live and it may not do this: it does not carry the ability the endpoint asks for, or it is the wrong kind of key for the endpoint. Retrying will not help. A customer whose key cannot start checks has to mint a new one.
404 Error There is nothing here for this key. A business that does not exist and a business this key may not see are the same answer, deliberately: a different answer for the two would let a stranger walk the integers and learn how many customers we have.
405 Error That address does not answer this method. The router refuses this one before the route group runs, so the request id is minted by the error renderer instead of by the middleware; the header is there either way.
429 Error Too many requests. retry_after is in the body and in the Retry-After header; wait that long, then try again. The cap that bites is per key.
500 Error Something broke on our side. The body never carries a trace, a file path or an exception name, not even on a deployment left in debug mode. Quote the X-Request-Id when you write to us.
503 Error The API is switched off right now, refused before anything is authenticated. retry_after is an hour. Back off and come back; do not treat it as a bad key and do not ask the person to re-enter anything.

GET /businesses/{id}

One business and where it stands

The call to make on every page load: what the business is, the one count that is the headline, whether anything is running right now, and when it was last checked. The headline is the rolling window over the newest four ISO weeks of our own sampling, or the free check when that is all there is, or nothing at all. Nothing at all is a null headline and the word none, never a zero: no sample means no information.

What goes in the address

What goes in the address: GET /businesses/{id}
Name Where Required What it means
id path Yes The business, in either spelling: the integer id this API has always taken, or the opaque public_id every payload now also carries. The two can never be confused, because a public_id is fourteen characters and always starts with a letter. A segment that is neither never reaches a controller, and a business you may not read answers the same 404 as one that does not exist, whichever spelling was tried.

What you send

Nothing to send.

What comes back

What comes back: GET /businesses/{id}
Answer Shape What it means
200 BusinessSummary The business and its summary.
401 Error No live key on this request. A missing header, a scheme that is not Bearer, a key we have never seen, a revoked key, an expired key, a key on a closed account and a partner key whose partner is gone all answer this same body: telling them apart would tell somebody holding a stolen key whether it is still worth trying. Ask the person to paste the key again; do not retry the same one.
403 Error The key is live and it may not do this: it does not carry the ability the endpoint asks for, or it is the wrong kind of key for the endpoint. Retrying will not help. A customer whose key cannot start checks has to mint a new one.
404 Error There is nothing here for this key. A business that does not exist and a business this key may not see are the same answer, deliberately: a different answer for the two would let a stranger walk the integers and learn how many customers we have.
405 Error That address does not answer this method. The router refuses this one before the route group runs, so the request id is minted by the error renderer instead of by the middleware; the header is there either way.
429 Error Too many requests. retry_after is in the body and in the Retry-After header; wait that long, then try again. The cap that bites is per key.
500 Error Something broke on our side. The body never carries a trace, a file path or an exception name, not even on a deployment left in debug mode. Quote the X-Request-Id when you write to us.
503 Error The API is switched off right now, refused before anything is authenticated. retry_after is an hour. Back off and come back; do not treat it as a bad key and do not ask the person to re-enter anything.

GET /businesses/{id}/weeks

The weekly ledger and the rolling window

Two lists, because they answer two questions: weeks is what each week's own sample said, rolling is what the last four weeks said together, and the second is where every headline is read from. A free check appears in weeks with kind free_check and never in rolling: it was a different size, so it neither widens nor narrows the usual range. Oldest first.

What goes in the address

What goes in the address: GET /businesses/{id}/weeks
Name Where Required What it means
id path Yes The business, in either spelling: the integer id this API has always taken, or the opaque public_id every payload now also carries. The two can never be confused, because a public_id is fourteen characters and always starts with a letter. A segment that is neither never reaches a controller, and a business you may not read answers the same 404 as one that does not exist, whichever spelling was tried.
weeks query No How many of the newest weeks to return.

What you send

Nothing to send.

What comes back

What comes back: GET /businesses/{id}/weeks
Answer Shape What it means
200 WeekLedger The ledger, oldest first.
401 Error No live key on this request. A missing header, a scheme that is not Bearer, a key we have never seen, a revoked key, an expired key, a key on a closed account and a partner key whose partner is gone all answer this same body: telling them apart would tell somebody holding a stolen key whether it is still worth trying. Ask the person to paste the key again; do not retry the same one.
403 Error The key is live and it may not do this: it does not carry the ability the endpoint asks for, or it is the wrong kind of key for the endpoint. Retrying will not help. A customer whose key cannot start checks has to mint a new one.
404 Error There is nothing here for this key. A business that does not exist and a business this key may not see are the same answer, deliberately: a different answer for the two would let a stranger walk the integers and learn how many customers we have.
405 Error That address does not answer this method. The router refuses this one before the route group runs, so the request id is minted by the error renderer instead of by the middleware; the header is there either way.
422 Error Something you sent could not be used, and error.fields names it. Nothing is ever silently clamped: limit=5000 is a refusal rather than a page of fifty, because a client that asked for five thousand and was handed fifty without being told has written a loop that never terminates.
429 Error Too many requests. retry_after is in the body and in the Retry-After header; wait that long, then try again. The cap that bites is per key.
500 Error Something broke on our side. The body never carries a trace, a file path or an exception name, not even on a deployment left in debug mode. Quote the X-Request-Id when you write to us.
503 Error The API is switched off right now, refused before anything is authenticated. retry_after is an hour. Back off and come back; do not treat it as a bad key and do not ask the person to re-enter anything.

GET /businesses/{id}/runs

The runs of this business, newest first

Cursor paginated on the id. Pass the previous page's next_before to get the next page; next_before is null on the last one. An offset would repeat or skip a row whenever a check finished between two calls, which on a list that grows at the front is the normal case rather than an edge one.

What goes in the address

What goes in the address: GET /businesses/{id}/runs
Name Where Required What it means
id path Yes The business, in either spelling: the integer id this API has always taken, or the opaque public_id every payload now also carries. The two can never be confused, because a public_id is fourteen characters and always starts with a letter. A segment that is neither never reaches a controller, and a business you may not read answers the same 404 as one that does not exist, whichever spelling was tried.
limit query No Runs per page.
before query No The id the previous page ended on.

What you send

Nothing to send.

What comes back

What comes back: GET /businesses/{id}/runs
Answer Shape What it means
200 RunPage One page of runs, newest first.
401 Error No live key on this request. A missing header, a scheme that is not Bearer, a key we have never seen, a revoked key, an expired key, a key on a closed account and a partner key whose partner is gone all answer this same body: telling them apart would tell somebody holding a stolen key whether it is still worth trying. Ask the person to paste the key again; do not retry the same one.
403 Error The key is live and it may not do this: it does not carry the ability the endpoint asks for, or it is the wrong kind of key for the endpoint. Retrying will not help. A customer whose key cannot start checks has to mint a new one.
404 Error There is nothing here for this key. A business that does not exist and a business this key may not see are the same answer, deliberately: a different answer for the two would let a stranger walk the integers and learn how many customers we have.
405 Error That address does not answer this method. The router refuses this one before the route group runs, so the request id is minted by the error renderer instead of by the middleware; the header is there either way.
422 Error Something you sent could not be used, and error.fields names it. Nothing is ever silently clamped: limit=5000 is a refusal rather than a page of fifty, because a client that asked for five thousand and was handed fifty without being told has written a loop that never terminates.
429 Error Too many requests. retry_after is in the body and in the Retry-After header; wait that long, then try again. The cap that bites is per key.
500 Error Something broke on our side. The body never carries a trace, a file path or an exception name, not even on a deployment left in debug mode. Quote the X-Request-Id when you write to us.
503 Error The API is switched off right now, refused before anything is authenticated. retry_after is an hour. Back off and come back; do not treat it as a bad key and do not ask the person to re-enter anything.

GET /businesses/{id}/runs/{run}

One run, its questions and every answer word for word

The whole of one check: the run with its counts, the questions it asked as it asked them, and every answer with the verdict the panel gave it. THIS IS THE ENDPOINT A CLIENT POLLS while a check is running, every couple of seconds, and it carries an ETag for exactly that: send the tag back as If-None-Match and an unchanged run answers 304 with no body. The tag moves when the status changes, when an answer lands, and when a person reviews an appearance, because a review turns a possible into a picked without any new answer arriving. A run of another business under this business's id is a 404.

What goes in the address

What goes in the address: GET /businesses/{id}/runs/{run}
Name Where Required What it means
id path Yes The business, in either spelling: the integer id this API has always taken, or the opaque public_id every payload now also carries. The two can never be confused, because a public_id is fourteen characters and always starts with a letter. A segment that is neither never reaches a controller, and a business you may not read answers the same 404 as one that does not exist, whichever spelling was tried.
run path Yes The run, in either spelling: the integer id, or the opaque public_id the same payloads carry.
If-None-Match header No The ETag of the copy you already have.

What you send

Nothing to send.

What comes back

What comes back: GET /businesses/{id}/runs/{run}
Answer Shape What it means
200 RunDetail The run, its questions and its answers.
304 Nothing has changed since the tag you sent. No body. Keep polling with the same tag.
401 Error No live key on this request. A missing header, a scheme that is not Bearer, a key we have never seen, a revoked key, an expired key, a key on a closed account and a partner key whose partner is gone all answer this same body: telling them apart would tell somebody holding a stolen key whether it is still worth trying. Ask the person to paste the key again; do not retry the same one.
403 Error The key is live and it may not do this: it does not carry the ability the endpoint asks for, or it is the wrong kind of key for the endpoint. Retrying will not help. A customer whose key cannot start checks has to mint a new one.
404 Error There is nothing here for this key. A business that does not exist and a business this key may not see are the same answer, deliberately: a different answer for the two would let a stranger walk the integers and learn how many customers we have.
405 Error That address does not answer this method. The router refuses this one before the route group runs, so the request id is minted by the error renderer instead of by the middleware; the header is there either way.
429 Error Too many requests. retry_after is in the body and in the Retry-After header; wait that long, then try again. The cap that bites is per key.
500 Error Something broke on our side. The body never carries a trace, a file path or an exception name, not even on a deployment left in debug mode. Quote the X-Request-Id when you write to us.
503 Error The API is switched off right now, refused before anything is authenticated. retry_after is an hour. Back off and come back; do not treat it as a bad key and do not ask the person to re-enter anything.

GET /businesses/{id}/answers

The answers themselves, word for word

Not a claim and not a summary: the assistant's own paragraph, with the business's name in it or without it. run=latest is the run the panel itself reads, which is the newest sampled run when a sampled week exists and the newest finished free check when none does. A business with nothing to show answers 200 with a null run_id and an empty list, never a 404: nothing checked yet is a state to draw, not an error to report.

What goes in the address

What goes in the address: GET /businesses/{id}/answers
Name Where Required What it means
id path Yes The business, in either spelling: the integer id this API has always taken, or the opaque public_id every payload now also carries. The two can never be confused, because a public_id is fourteen characters and always starts with a letter. A segment that is neither never reaches a controller, and a business you may not read answers the same 404 as one that does not exist, whichever spelling was tried.
run query No The word latest, or a run in either spelling: the integer id, or the opaque public_id the same payloads carry.
named query No all is every verdict in the run. yes is the verdict picked. no is the verdict not_picked, and nothing else: an answer that never arrived and an empty AI Overview did not fail to name anybody, so they are in neither list.
limit query No Answers per call. A Growth week is 275 of them.

What you send

Nothing to send.

What comes back

What comes back: GET /businesses/{id}/answers
Answer Shape What it means
200 AnswerPage The run's answers, oldest first.
401 Error No live key on this request. A missing header, a scheme that is not Bearer, a key we have never seen, a revoked key, an expired key, a key on a closed account and a partner key whose partner is gone all answer this same body: telling them apart would tell somebody holding a stolen key whether it is still worth trying. Ask the person to paste the key again; do not retry the same one.
403 Error The key is live and it may not do this: it does not carry the ability the endpoint asks for, or it is the wrong kind of key for the endpoint. Retrying will not help. A customer whose key cannot start checks has to mint a new one.
404 Error There is nothing here for this key. A business that does not exist and a business this key may not see are the same answer, deliberately: a different answer for the two would let a stranger walk the integers and learn how many customers we have.
405 Error That address does not answer this method. The router refuses this one before the route group runs, so the request id is minted by the error renderer instead of by the middleware; the header is there either way.
422 Error Something you sent could not be used, and error.fields names it. Nothing is ever silently clamped: limit=5000 is a refusal rather than a page of fifty, because a client that asked for five thousand and was handed fifty without being told has written a loop that never terminates.
429 Error Too many requests. retry_after is in the body and in the Retry-After header; wait that long, then try again. The cap that bites is per key.
500 Error Something broke on our side. The body never carries a trace, a file path or an exception name, not even on a deployment left in debug mode. Quote the X-Request-Id when you write to us.
503 Error The API is switched off right now, refused before anything is authenticated. retry_after is an hour. Back off and come back; do not treat it as a bad key and do not ask the person to re-enter anything.

GET /businesses/{id}/blockers

What is in the way, in the order an owner should read it

The task list a client turns into a panel on somebody's own dashboard. The order is the product: failures before warnings, then the weight the research gives each check, and a client that sorted them itself would be telling a business owner to fix the cheap thing first. blockers are the tasks; rest is everything else we looked at and found nothing wrong with. A run whose checks have not run is two empty lists and a null checked_at, never a clean bill of health. Nothing here publishes anything anywhere: every fix is the owner's own act on their own site.

What goes in the address

What goes in the address: GET /businesses/{id}/blockers
Name Where Required What it means
id path Yes The business, in either spelling: the integer id this API has always taken, or the opaque public_id every payload now also carries. The two can never be confused, because a public_id is fourteen characters and always starts with a letter. A segment that is neither never reaches a controller, and a business you may not read answers the same 404 as one that does not exist, whichever spelling was tried.

What you send

Nothing to send.

What comes back

What comes back: GET /businesses/{id}/blockers
Answer Shape What it means
200 BlockerReport The blockers and the rest of the readings.
401 Error No live key on this request. A missing header, a scheme that is not Bearer, a key we have never seen, a revoked key, an expired key, a key on a closed account and a partner key whose partner is gone all answer this same body: telling them apart would tell somebody holding a stolen key whether it is still worth trying. Ask the person to paste the key again; do not retry the same one.
403 Error The key is live and it may not do this: it does not carry the ability the endpoint asks for, or it is the wrong kind of key for the endpoint. Retrying will not help. A customer whose key cannot start checks has to mint a new one.
404 Error There is nothing here for this key. A business that does not exist and a business this key may not see are the same answer, deliberately: a different answer for the two would let a stranger walk the integers and learn how many customers we have.
405 Error That address does not answer this method. The router refuses this one before the route group runs, so the request id is minted by the error renderer instead of by the middleware; the header is there either way.
429 Error Too many requests. retry_after is in the body and in the Retry-After header; wait that long, then try again. The cap that bites is per key.
500 Error Something broke on our side. The body never carries a trace, a file path or an exception name, not even on a deployment left in debug mode. Quote the X-Request-Id when you write to us.
503 Error The API is switched off right now, refused before anything is authenticated. retry_after is an hour. Back off and come back; do not treat it as a bad key and do not ask the person to re-enter anything.

GET /businesses/{id}/named

The businesses AI named beside this one

Growth only. A Starter, a Free business and a business on no plan all answer 404, exactly as the panel does: this product does not sell by showing somebody a door they cannot open, and a 404 also tells a stranger nothing about which plan a business is on. Every row is a name and how many answers named it, this week against four weeks ago. The rows arrive with the biggest count first because a list has to be in some order; the order is not a claim and there is no key here that says it is.

What goes in the address

What goes in the address: GET /businesses/{id}/named
Name Where Required What it means
id path Yes The business, in either spelling: the integer id this API has always taken, or the opaque public_id every payload now also carries. The two can never be confused, because a public_id is fourteen characters and always starts with a letter. A segment that is neither never reaches a controller, and a business you may not read answers the same 404 as one that does not exist, whichever spelling was tried.

What you send

Nothing to send.

What comes back

What comes back: GET /businesses/{id}/named
Answer Shape What it means
200 NamedLedger The names and their counts.
401 Error No live key on this request. A missing header, a scheme that is not Bearer, a key we have never seen, a revoked key, an expired key, a key on a closed account and a partner key whose partner is gone all answer this same body: telling them apart would tell somebody holding a stolen key whether it is still worth trying. Ask the person to paste the key again; do not retry the same one.
403 Error The key is live and it may not do this: it does not carry the ability the endpoint asks for, or it is the wrong kind of key for the endpoint. Retrying will not help. A customer whose key cannot start checks has to mint a new one.
404 Error There is nothing here for this key. A business that does not exist and a business this key may not see are the same answer, deliberately: a different answer for the two would let a stranger walk the integers and learn how many customers we have.
405 Error That address does not answer this method. The router refuses this one before the route group runs, so the request id is minted by the error renderer instead of by the middleware; the header is there either way.
429 Error Too many requests. retry_after is in the body and in the Retry-After header; wait that long, then try again. The cap that bites is per key.
500 Error Something broke on our side. The body never carries a trace, a file path or an exception name, not even on a deployment left in debug mode. Quote the X-Request-Id when you write to us.
503 Error The API is switched off right now, refused before anything is authenticated. retry_after is an hour. Back off and come back; do not treat it as a bad key and do not ask the person to re-enter anything.

GET /businesses/{id}/receipts

People who arrived from an assistant

The other half of the proof: the weeks say how often assistants named the business, this says how many people then arrived at the business's own site from one. A count of arrivals and nothing else, over a fixed window of 30 days, with every day printed including the empty ones. There is no address, no user agent, no cookie and no identifier in any of it, because the snippet never wrote one.

What goes in the address

What goes in the address: GET /businesses/{id}/receipts
Name Where Required What it means
id path Yes The business, in either spelling: the integer id this API has always taken, or the opaque public_id every payload now also carries. The two can never be confused, because a public_id is fourteen characters and always starts with a letter. A segment that is neither never reaches a controller, and a business you may not read answers the same 404 as one that does not exist, whichever spelling was tried.

What you send

Nothing to send.

What comes back

What comes back: GET /businesses/{id}/receipts
Answer Shape What it means
200 Receipts The visits, by assistant and by day.
401 Error No live key on this request. A missing header, a scheme that is not Bearer, a key we have never seen, a revoked key, an expired key, a key on a closed account and a partner key whose partner is gone all answer this same body: telling them apart would tell somebody holding a stolen key whether it is still worth trying. Ask the person to paste the key again; do not retry the same one.
403 Error The key is live and it may not do this: it does not carry the ability the endpoint asks for, or it is the wrong kind of key for the endpoint. Retrying will not help. A customer whose key cannot start checks has to mint a new one.
404 Error There is nothing here for this key. A business that does not exist and a business this key may not see are the same answer, deliberately: a different answer for the two would let a stranger walk the integers and learn how many customers we have.
405 Error That address does not answer this method. The router refuses this one before the route group runs, so the request id is minted by the error renderer instead of by the middleware; the header is there either way.
429 Error Too many requests. retry_after is in the body and in the Retry-After header; wait that long, then try again. The cap that bites is per key.
500 Error Something broke on our side. The body never carries a trace, a file path or an exception name, not even on a deployment left in debug mode. Quote the X-Request-Id when you write to us.
503 Error The API is switched off right now, refused before anything is authenticated. retry_after is an hour. Back off and come back; do not treat it as a bad key and do not ask the person to re-enter anything.

GET /businesses/{id}/snippet

The receipts snippet for this business

The one step of this product that asks a business owner to touch their own website, handed to a client so that they never have to. The site key, the two URLs built out of this deployment's own routes, whether we are recording at all right now, and one sentence in the owner's language to print beside the switch. The URLs are built rather than written down, so a client pointed at staging never posts a live shop's visits into a test database.

What goes in the address

What goes in the address: GET /businesses/{id}/snippet
Name Where Required What it means
id path Yes The business, in either spelling: the integer id this API has always taken, or the opaque public_id every payload now also carries. The two can never be confused, because a public_id is fourteen characters and always starts with a letter. A segment that is neither never reaches a controller, and a business you may not read answers the same 404 as one that does not exist, whichever spelling was tried.

What you send

Nothing to send.

What comes back

What comes back: GET /businesses/{id}/snippet
Answer Shape What it means
200 Snippet The site key, the two URLs and the instruction.
401 Error No live key on this request. A missing header, a scheme that is not Bearer, a key we have never seen, a revoked key, an expired key, a key on a closed account and a partner key whose partner is gone all answer this same body: telling them apart would tell somebody holding a stolen key whether it is still worth trying. Ask the person to paste the key again; do not retry the same one.
403 Error The key is live and it may not do this: it does not carry the ability the endpoint asks for, or it is the wrong kind of key for the endpoint. Retrying will not help. A customer whose key cannot start checks has to mint a new one.
404 Error There is nothing here for this key. A business that does not exist and a business this key may not see are the same answer, deliberately: a different answer for the two would let a stranger walk the integers and learn how many customers we have.
405 Error That address does not answer this method. The router refuses this one before the route group runs, so the request id is minted by the error renderer instead of by the middleware; the header is there either way.
429 Error Too many requests. retry_after is in the body and in the Retry-After header; wait that long, then try again. The cap that bites is per key.
500 Error Something broke on our side. The body never carries a trace, a file path or an exception name, not even on a deployment left in debug mode. Quote the X-Request-Id when you write to us.
503 Error The API is switched off right now, refused before anything is authenticated. retry_after is an hour. Back off and come back; do not treat it as a bad key and do not ask the person to re-enter anything.

GET /businesses/{id}/slices

The window cut by assistant or by question

The same window the weeks endpoint counts, cut one way or the other: one row per assistant, which is the panel's matrix card, or one row per question, which is the panel's questions page. Every row carries the n it was measured over, the usual range as whole counts of that same n, and the sentence we would print ourselves about how much weight the row will bear, because a cut of a window is small enough that some of its rows cannot be read on their own. A week nobody asked that assistant or that question in says asked false and counts in no total: it is not a zero, since nobody asked. There is one range per row and never one per week, and there is no difference between two weeks anywhere in it.

What goes in the address

What goes in the address: GET /businesses/{id}/slices
Name Where Required What it means
id path Yes The business, in either spelling: the integer id this API has always taken, or the opaque public_id every payload now also carries. The two can never be confused, because a public_id is fourteen characters and always starts with a letter. A segment that is neither never reaches a controller, and a business you may not read answers the same 404 as one that does not exist, whichever spelling was tried.
dimension query No Which way to cut the window.
weeks query No How many of the newest weeks the window covers, as the panel's own range control offers them. 0 is every week we hold. Anything else is refused rather than corrected.

What you send

Nothing to send.

What comes back

What comes back: GET /businesses/{id}/slices
Answer Shape What it means
200 SliceReport The cut, one row per assistant or per question.
401 Error No live key on this request. A missing header, a scheme that is not Bearer, a key we have never seen, a revoked key, an expired key, a key on a closed account and a partner key whose partner is gone all answer this same body: telling them apart would tell somebody holding a stolen key whether it is still worth trying. Ask the person to paste the key again; do not retry the same one.
403 Error The key is live and it may not do this: it does not carry the ability the endpoint asks for, or it is the wrong kind of key for the endpoint. Retrying will not help. A customer whose key cannot start checks has to mint a new one.
404 Error There is nothing here for this key. A business that does not exist and a business this key may not see are the same answer, deliberately: a different answer for the two would let a stranger walk the integers and learn how many customers we have.
405 Error That address does not answer this method. The router refuses this one before the route group runs, so the request id is minted by the error renderer instead of by the middleware; the header is there either way.
422 Error Something you sent could not be used, and error.fields names it. Nothing is ever silently clamped: limit=5000 is a refusal rather than a page of fifty, because a client that asked for five thousand and was handed fifty without being told has written a loop that never terminates.
429 Error Too many requests. retry_after is in the body and in the Retry-After header; wait that long, then try again. The cap that bites is per key.
500 Error Something broke on our side. The body never carries a trace, a file path or an exception name, not even on a deployment left in debug mode. Quote the X-Request-Id when you write to us.
503 Error The API is switched off right now, refused before anything is authenticated. retry_after is an hour. Back off and come back; do not treat it as a bad key and do not ask the person to re-enter anything.

GET /businesses/{id}/reports

The months that have closed, with their frozen figures

One row a closed month, newest first: the count of answers that named the business that month, the n it came out of, and the two ends of the usual range as whole counts of that same n. Every figure was measured once, on the day the month closed, and none of them ever moves again except by an amendment, which is stamped on the row. A month still running is NOT in this list: it has no frozen figures behind it, and a row that changed every Monday under the name of a month would be the one thing a report may never be. For the live measurement, read the weeks and the slices, which say out loud that they are live.

What goes in the address

What goes in the address: GET /businesses/{id}/reports
Name Where Required What it means
id path Yes The business, in either spelling: the integer id this API has always taken, or the opaque public_id every payload now also carries. The two can never be confused, because a public_id is fourteen characters and always starts with a letter. A segment that is neither never reaches a controller, and a business you may not read answers the same 404 as one that does not exist, whichever spelling was tried.

What you send

Nothing to send.

What comes back

What comes back: GET /businesses/{id}/reports
Answer Shape What it means
200 ReportList The closed months, newest first.
401 Error No live key on this request. A missing header, a scheme that is not Bearer, a key we have never seen, a revoked key, an expired key, a key on a closed account and a partner key whose partner is gone all answer this same body: telling them apart would tell somebody holding a stolen key whether it is still worth trying. Ask the person to paste the key again; do not retry the same one.
403 Error The key is live and it may not do this: it does not carry the ability the endpoint asks for, or it is the wrong kind of key for the endpoint. Retrying will not help. A customer whose key cannot start checks has to mint a new one.
404 Error There is nothing here for this key. A business that does not exist and a business this key may not see are the same answer, deliberately: a different answer for the two would let a stranger walk the integers and learn how many customers we have.
405 Error That address does not answer this method. The router refuses this one before the route group runs, so the request id is minted by the error renderer instead of by the middleware; the header is there either way.
429 Error Too many requests. retry_after is in the body and in the Retry-After header; wait that long, then try again. The cap that bites is per key.
500 Error Something broke on our side. The body never carries a trace, a file path or an exception name, not even on a deployment left in debug mode. Quote the X-Request-Id when you write to us.
503 Error The API is switched off right now, refused before anything is authenticated. retry_after is an hour. Back off and come back; do not treat it as a bad key and do not ask the person to re-enter anything.

GET /businesses/{id}/reports/{period}

One closed month, exactly as it was frozen

The whole document the customer holds on paper: the month's count and its usual range, every sampled Monday with its own count and its cut by assistant, what was in the way and what got fixed, who arrived from an assistant, and the two answers the report quotes. It is read off the stored document and nothing is counted again, so a match a person confirms in November does not move a September an agency has already forwarded. A month that never closed, and a month of another business, are the same 404. The PDF itself is not served over this API in v1.

What goes in the address

What goes in the address: GET /businesses/{id}/reports/{period}
Name Where Required What it means
id path Yes The business, in either spelling: the integer id this API has always taken, or the opaque public_id every payload now also carries. The two can never be confused, because a public_id is fourteen characters and always starts with a letter. A segment that is neither never reaches a controller, and a business you may not read answers the same 404 as one that does not exist, whichever spelling was tried.
period path Yes The month, as four digits, a hyphen and two. It is the spelling the report's own filename carries and the one the archive links, so a client stores one form of a month and no other. A segment that is not that shape never reaches a controller.

What you send

Nothing to send.

What comes back

What comes back: GET /businesses/{id}/reports/{period}
Answer Shape What it means
200 MonthReport The frozen document.
401 Error No live key on this request. A missing header, a scheme that is not Bearer, a key we have never seen, a revoked key, an expired key, a key on a closed account and a partner key whose partner is gone all answer this same body: telling them apart would tell somebody holding a stolen key whether it is still worth trying. Ask the person to paste the key again; do not retry the same one.
403 Error The key is live and it may not do this: it does not carry the ability the endpoint asks for, or it is the wrong kind of key for the endpoint. Retrying will not help. A customer whose key cannot start checks has to mint a new one.
404 Error There is nothing here for this key. A business that does not exist and a business this key may not see are the same answer, deliberately: a different answer for the two would let a stranger walk the integers and learn how many customers we have.
405 Error That address does not answer this method. The router refuses this one before the route group runs, so the request id is minted by the error renderer instead of by the middleware; the header is there either way.
429 Error Too many requests. retry_after is in the body and in the Retry-After header; wait that long, then try again. The cap that bites is per key.
500 Error Something broke on our side. The body never carries a trace, a file path or an exception name, not even on a deployment left in debug mode. Quote the X-Request-Id when you write to us.
503 Error The API is switched off right now, refused before anything is authenticated. retry_after is an hour. Back off and come back; do not treat it as a bad key and do not ask the person to re-enter anything.

POST /businesses/{id}/checks

Start a check on this business

Needs the checks ability. The money rules are the free checker's own, never a softer copy: a fresh run of any kind inside the dedupe window is reused rather than bought again, a run that failed is retried and keeps the answers it already collected, and a day whose budget is committed dates the run for tomorrow and answers queued. There is no request body: the business is in the path, and a body over 16 KB is refused with body_too_large before the controller runs. Poll run.poll_url until the run's status is done or failed. run.receipt_url is a public results page a client may open for the owner. A partner key may only start a check on a shop that is INSTALLED: an uninstalled shop stays readable, because its history is why the row was kept, and answers 409 connection_not_active here.

What goes in the address

What goes in the address: POST /businesses/{id}/checks
Name Where Required What it means
id path Yes The business, in either spelling: the integer id this API has always taken, or the opaque public_id every payload now also carries. The two can never be confused, because a public_id is fourteen characters and always starts with a letter. A segment that is neither never reaches a controller, and a business you may not read answers the same 404 as one that does not exist, whichever spelling was tried.

What you send

Nothing to send.

What comes back

What comes back: POST /businesses/{id}/checks
Answer Shape What it means
200 CheckAccepted reused: a fresh run already existed and you were handed that one. Nothing was created and nothing was spent.
202 CheckAccepted started, retried or queued. The run exists and is on its way; poll it. queued means today's budget is committed and the run is dated for tomorrow morning.
401 Error No live key on this request. A missing header, a scheme that is not Bearer, a key we have never seen, a revoked key, an expired key, a key on a closed account and a partner key whose partner is gone all answer this same body: telling them apart would tell somebody holding a stolen key whether it is still worth trying. Ask the person to paste the key again; do not retry the same one.
403 Error The key is live and it may not do this: it does not carry the ability the endpoint asks for, or it is the wrong kind of key for the endpoint. Retrying will not help. A customer whose key cannot start checks has to mint a new one.
404 Error There is nothing here for this key. A business that does not exist and a business this key may not see are the same answer, deliberately: a different answer for the two would let a stranger walk the integers and learn how many customers we have.
405 Error That address does not answer this method. The router refuses this one before the route group runs, so the request id is minted by the error renderer instead of by the middleware; the header is there either way.
409 Error connection_not_active: a partner key asked for a check on a shop that is not installed. Nothing was written and nothing was spent. Reading that shop is still allowed.
413 Error The request body is over 16 KB, refused in front of the controller.
429 Error rate_limited (20 a minute per key) or checks_limit_reached (the day cap of 3, per key on a customer key and per shop on a partner key). Both carry retry_after in the body and a Retry-After header.
500 Error Something broke on our side. The body never carries a trace, a file path or an exception name, not even on a deployment left in debug mode. Quote the X-Request-Id when you write to us.
503 Error The API is switched off right now, refused before anything is authenticated. retry_after is an hour. Back off and come back; do not treat it as a bad key and do not ask the person to re-enter anything.

GET /businesses/{id}/pages/queue

The pages this site may put up, and take down

Needs the pages ability. Two lists, and both of them are empty until a customer has approved something. publish is every page this business approved on the plugin route whose approvals row exists, is not an undo and has not been reverted; html is the text of that approvals row itself, which is the body with the disclosure line as its last paragraph and the one copy of it that can never be edited. unpublish is every page whose owner pressed Undo and which this site has not reported taking down yet, with the approvals row of the undo and the post id you told us about. Ordered by our own id, so two syncs agree and a half finished one can be repeated. Never cached: the body is one customer's own prose.

What goes in the address

What goes in the address: GET /businesses/{id}/pages/queue
Name Where Required What it means
id path Yes The business, in either spelling: the integer id this API has always taken, or the opaque public_id every payload now also carries. The two can never be confused, because a public_id is fourteen characters and always starts with a letter. A segment that is neither never reaches a controller, and a business you may not read answers the same 404 as one that does not exist, whichever spelling was tried.

What you send

Nothing to send.

What comes back

What comes back: GET /businesses/{id}/pages/queue
Answer Shape What it means
200 PagesQueue The two lists, either or both of them possibly empty.
401 Error No live key on this request. A missing header, a scheme that is not Bearer, a key we have never seen, a revoked key, an expired key, a key on a closed account and a partner key whose partner is gone all answer this same body: telling them apart would tell somebody holding a stolen key whether it is still worth trying. Ask the person to paste the key again; do not retry the same one.
403 Error The key is live and it may not do this: it does not carry the ability the endpoint asks for, or it is the wrong kind of key for the endpoint. Retrying will not help. A customer whose key cannot start checks has to mint a new one.
404 Error There is nothing here for this key. A business that does not exist and a business this key may not see are the same answer, deliberately: a different answer for the two would let a stranger walk the integers and learn how many customers we have.
405 Error That address does not answer this method. The router refuses this one before the route group runs, so the request id is minted by the error renderer instead of by the middleware; the header is there either way.
429 Error Too many requests. retry_after is in the body and in the Retry-After header; wait that long, then try again. The cap that bites is per key.
500 Error Something broke on our side. The body never carries a trace, a file path or an exception name, not even on a deployment left in debug mode. Quote the X-Request-Id when you write to us.
503 Error The API is switched off right now, refused before anything is authenticated. retry_after is an hour. Back off and come back; do not treat it as a bad key and do not ask the person to re-enter anything.

POST /businesses/{id}/pages/{page}/report

Tell us what this site did with a page

Needs the pages ability. Report one item off the queue: the action you took, whether it worked, the post id you made and the address it is at. The whole body is appended to the customer's own approvals row word for word, so they can read what their site said. On a done publish the page is marked as landed once and we then go and read that address back ourselves, and only that read writes the receipt. On a done unpublish the page is marked as taken down and the undo row is read back the same way. On failed nothing moves and the item stays on the queue, so the next sync tries again by itself. Reporting the same thing twice is safe: the second report is recorded and changes nothing. A page that is not this business's, or is not waiting for the action you named, is a 404 and writes nothing at all. The address has to be an https one on the business's own website, because it is what we print to them and what we are about to fetch. A body over 16 KB is refused with body_too_large before the controller runs.

What goes in the address

What goes in the address: POST /businesses/{id}/pages/{page}/report
Name Where Required What it means
id path Yes The business, in either spelling: the integer id this API has always taken, or the opaque public_id every payload now also carries. The two can never be confused, because a public_id is fourteen characters and always starts with a letter. A segment that is neither never reaches a controller, and a business you may not read answers the same 404 as one that does not exist, whichever spelling was tried.
page path Yes The page, as the queue named it: the opaque public_id, fourteen characters starting on a letter. There is no second spelling, because the queue is the only place a client ever learns of a page and it hands out this one. A page that is not this business's answers the same 404 as one that does not exist.

What you send

A body is required.

What you send: POST /businesses/{id}/pages/{page}/report
Field Type Required What it means
action string Yes Which of the two lists this item came off.
outcome string Yes done means the site did it. failed means it could not, nothing moves, and the item stays on the queue for the next sync.
post_id integer or null No The id of the post you made, or the one you updated.
url string or null No Where the page is. Required on a done publish, because it is what we print to the customer and what we then read back ourselves. It has to be an https address on the business's own website.
message string or null No Anything the site wants to add, above all on a failure. It is stored with the rest of the body, word for word.

What comes back

What comes back: POST /businesses/{id}/pages/{page}/report
Answer Shape What it means
200 PageReportRecorded The report was recorded. state is what the page is now, so a client can stop asking about one that is done rather than infer it.
401 Error No live key on this request. A missing header, a scheme that is not Bearer, a key we have never seen, a revoked key, an expired key, a key on a closed account and a partner key whose partner is gone all answer this same body: telling them apart would tell somebody holding a stolen key whether it is still worth trying. Ask the person to paste the key again; do not retry the same one.
403 Error The key is live and it may not do this: it does not carry the ability the endpoint asks for, or it is the wrong kind of key for the endpoint. Retrying will not help. A customer whose key cannot start checks has to mint a new one.
404 Error There is nothing here for this key. A business that does not exist and a business this key may not see are the same answer, deliberately: a different answer for the two would let a stranger walk the integers and learn how many customers we have.
405 Error That address does not answer this method. The router refuses this one before the route group runs, so the request id is minted by the error renderer instead of by the middleware; the header is there either way.
413 Error The request body is over 16 KB, refused in front of the controller.
422 Error Something you sent could not be used, and error.fields names it. Nothing is ever silently clamped: limit=5000 is a refusal rather than a page of fifty, because a client that asked for five thousand and was handed fifty without being told has written a loop that never terminates.
429 Error Too many requests. retry_after is in the body and in the Retry-After header; wait that long, then try again. The cap that bites is per key.
500 Error Something broke on our side. The body never carries a trace, a file path or an exception name, not even on a deployment left in debug mode. Quote the X-Request-Id when you write to us.
503 Error The API is switched off right now, refused before anything is authenticated. retry_after is an hour. Back off and come back; do not treat it as a bad key and do not ask the person to re-enter anything.

GET /businesses/{id}/partners

The partners list this site may show

Needs the partner_links ability. The whole of what goes on a partners page: the disclosure sentence to print above the list, the rel the owner chose for the links on their own site, the address of the record on picksyou.com, the page of their own site that shows it when one is registered, and one entry per live pair. enabled is false with an empty list when this business has no partners page, which is a true answer and not an error: print nothing. note is THIS business's own sentence about that partner and never the partner's about them. Nothing else about a partner is in it: no id, no plan, no count. Print what you last read and never fetch this on a visitor's page load; a public page must not wait on us. Never cached by anybody else: the body is two businesses' own words about each other.

What goes in the address

What goes in the address: GET /businesses/{id}/partners
Name Where Required What it means
id path Yes The business, in either spelling: the integer id this API has always taken, or the opaque public_id every payload now also carries. The two can never be confused, because a public_id is fourteen characters and always starts with a letter. A segment that is neither never reaches a controller, and a business you may not read answers the same 404 as one that does not exist, whichever spelling was tried.

What you send

Nothing to send.

What comes back

What comes back: GET /businesses/{id}/partners
Answer Shape What it means
200 PartnersList The list, possibly empty and possibly not enabled at all.
401 Error No live key on this request. A missing header, a scheme that is not Bearer, a key we have never seen, a revoked key, an expired key, a key on a closed account and a partner key whose partner is gone all answer this same body: telling them apart would tell somebody holding a stolen key whether it is still worth trying. Ask the person to paste the key again; do not retry the same one.
403 Error The key is live and it may not do this: it does not carry the ability the endpoint asks for, or it is the wrong kind of key for the endpoint. Retrying will not help. A customer whose key cannot start checks has to mint a new one.
404 Error There is nothing here for this key. A business that does not exist and a business this key may not see are the same answer, deliberately: a different answer for the two would let a stranger walk the integers and learn how many customers we have.
405 Error That address does not answer this method. The router refuses this one before the route group runs, so the request id is minted by the error renderer instead of by the middleware; the header is there either way.
429 Error Too many requests. retry_after is in the body and in the Retry-After header; wait that long, then try again. The cap that bites is per key.
500 Error Something broke on our side. The body never carries a trace, a file path or an exception name, not even on a deployment left in debug mode. Quote the X-Request-Id when you write to us.
503 Error The API is switched off right now, refused before anything is authenticated. retry_after is an hour. Back off and come back; do not treat it as a bad key and do not ask the person to re-enter anything.

POST /businesses/{id}/partners/page

Say which page of this site shows the list

Needs the partner_links ability. Register the permalink of the page showing the partners list, or send url null to clear it, which is what to send when the owner stops showing the list or deletes the page. url has to be in the body either way: clearing a registration is a real instruction and not an oversight, so a body that left the field out is a 422. The address has to be an https one on the business's own website and one this server is willing to fetch; anything else is a 422 that writes nothing at all. That address is the only fact about the site we hold, and it is what our own read-backs fetch: we look for an anchor to the partner's host and for the disclosure sentence, and that read is what writes the receipt. Registering a page changes what happens next and never what happened, so pairs already recorded against picksyou.com stay recorded there. A body over 16 KB is refused with body_too_large before the controller runs.

What goes in the address

What goes in the address: POST /businesses/{id}/partners/page
Name Where Required What it means
id path Yes The business, in either spelling: the integer id this API has always taken, or the opaque public_id every payload now also carries. The two can never be confused, because a public_id is fourteen characters and always starts with a letter. A segment that is neither never reaches a controller, and a business you may not read answers the same 404 as one that does not exist, whichever spelling was tried.

What you send

A body is required.

What you send: POST /businesses/{id}/partners/page
Field Type Required What it means
url string or null Yes The permalink of the page showing the list, over https and on the business's own website, or null to clear it. The field itself is required either way.
post_id integer or null No The id of the post on your side, if your platform has one.

What comes back

What comes back: POST /businesses/{id}/partners/page
Answer Shape What it means
200 PartnersPageRegistered The address we now hold for this site, or nulls.
401 Error No live key on this request. A missing header, a scheme that is not Bearer, a key we have never seen, a revoked key, an expired key, a key on a closed account and a partner key whose partner is gone all answer this same body: telling them apart would tell somebody holding a stolen key whether it is still worth trying. Ask the person to paste the key again; do not retry the same one.
403 Error The key is live and it may not do this: it does not carry the ability the endpoint asks for, or it is the wrong kind of key for the endpoint. Retrying will not help. A customer whose key cannot start checks has to mint a new one.
404 Error There is nothing here for this key. A business that does not exist and a business this key may not see are the same answer, deliberately: a different answer for the two would let a stranger walk the integers and learn how many customers we have.
405 Error That address does not answer this method. The router refuses this one before the route group runs, so the request id is minted by the error renderer instead of by the middleware; the header is there either way.
413 Error The request body is over 16 KB, refused in front of the controller.
422 Error Something you sent could not be used, and error.fields names it. Nothing is ever silently clamped: limit=5000 is a refusal rather than a page of fifty, because a client that asked for five thousand and was handed fifty without being told has written a loop that never terminates.
429 Error Too many requests. retry_after is in the body and in the Retry-After header; wait that long, then try again. The cap that bites is per key.
500 Error Something broke on our side. The body never carries a trace, a file path or an exception name, not even on a deployment left in debug mode. Quote the X-Request-Id when you write to us.
503 Error The API is switched off right now, refused before anything is authenticated. retry_after is an hour. Back off and come back; do not treat it as a bad key and do not ask the person to re-enter anything.

GET /partner/connections

Every shop this partner has, newest first

Cursor paginated on the id, because a partner walks this list while merchants are installing and uninstalling and an offset page two would skip or repeat rows as the table moves underneath it. next_before is null on the last page.

What goes in the address

What goes in the address: GET /partner/connections
Name Where Required What it means
status query No Only shops in this state.
limit query No Connections per page.
before query No The id the previous page ended on.

What you send

Nothing to send.

What comes back

What comes back: GET /partner/connections
Answer Shape What it means
200 ConnectionPage One page of connections, newest first.
401 Error No live key on this request. A missing header, a scheme that is not Bearer, a key we have never seen, a revoked key, an expired key, a key on a closed account and a partner key whose partner is gone all answer this same body: telling them apart would tell somebody holding a stolen key whether it is still worth trying. Ask the person to paste the key again; do not retry the same one.
403 Error The key is live and it may not do this: it does not carry the ability the endpoint asks for, or it is the wrong kind of key for the endpoint. Retrying will not help. A customer whose key cannot start checks has to mint a new one.
404 Error There is nothing here for this key. A business that does not exist and a business this key may not see are the same answer, deliberately: a different answer for the two would let a stranger walk the integers and learn how many customers we have.
405 Error That address does not answer this method. The router refuses this one before the route group runs, so the request id is minted by the error renderer instead of by the middleware; the header is there either way.
422 Error Something you sent could not be used, and error.fields names it. Nothing is ever silently clamped: limit=5000 is a refusal rather than a page of fifty, because a client that asked for five thousand and was handed fifty without being told has written a loop that never terminates.
429 Error Too many requests. retry_after is in the body and in the Retry-After header; wait that long, then try again. The cap that bites is per key.
500 Error Something broke on our side. The body never carries a trace, a file path or an exception name, not even on a deployment left in debug mode. Quote the X-Request-Id when you write to us.
503 Error The API is switched off right now, refused before anything is authenticated. retry_after is an hour. Back off and come back; do not treat it as a bad key and do not ask the person to re-enter anything.

POST /partner/connections

Provision a shop

Idempotent on (partner, external_id), because that is what an app store sends: a platform replays its install webhook whenever it did not hear back, and a merchant who uninstalls on Friday and installs again on Monday sends the same identifier both times. A connection that is already here comes back with 200 and is not rewritten; an uninstalled one is switched back on with its whole history under it, without its old plan, because a reinstall is not a payment. 201 means this call wrote the row. The business fields are the free checker's own rules, so both doors feeding one table cannot disagree about what a business row may hold.

What goes in the address

Nothing in the address.

What you send

A body is required.

What you send: POST /partner/connections
Field Type Required What it means
external_id string Yes Your own identifier for the merchant. The handle every later call uses.
name string Yes The business name as the merchant writes it.
website string or null No With or without a scheme; it is reduced to a bare host on our side, exactly as a typed one is.
city string Yes
country string Yes ISO 3166-1 alpha-2, and it must be a country the AI Overview vendor can search in: a country we cannot search is a country we cannot answer about.
language string Yes The language the assistants are asked in. English only since 2026-09-05; any other value is refused with 422 and error.code validation_failed.
category string Yes A pack slug, or other. Free text lines of business are checked in a general frame and say so through frame_uncertain.
category_label string or null No Required when category is other. Letters, spaces and hyphens, up to 40.
meta object or null No Anything you want to remember about this shop: a plan name on your side, a theme, an install source. At most 2 KB once encoded. Nothing personal: it survives an erasure.

What comes back

What comes back: POST /partner/connections
Answer Shape What it means
200 ConnectionWithBusiness This shop was already connected. Nothing was written.
201 ConnectionWithBusiness The connection was created by this call.
401 Error No live key on this request. A missing header, a scheme that is not Bearer, a key we have never seen, a revoked key, an expired key, a key on a closed account and a partner key whose partner is gone all answer this same body: telling them apart would tell somebody holding a stolen key whether it is still worth trying. Ask the person to paste the key again; do not retry the same one.
403 Error The key is live and it may not do this: it does not carry the ability the endpoint asks for, or it is the wrong kind of key for the endpoint. Retrying will not help. A customer whose key cannot start checks has to mint a new one.
404 Error There is nothing here for this key. A business that does not exist and a business this key may not see are the same answer, deliberately: a different answer for the two would let a stranger walk the integers and learn how many customers we have.
405 Error That address does not answer this method. The router refuses this one before the route group runs, so the request id is minted by the error renderer instead of by the middleware; the header is there either way.
409 Error business_owned: the business this shop dedupes onto already belongs to a picksyou.com account, and linking the two is a decision with a human in it rather than a guess inside a webhook. business_connected: another partner, or another shop of yours, already points at that business, and two live connections on one business would make "who is paying for this week" unanswerable. Neither writes anything.
413 Error The request body is over 16 KB, refused in front of the controller.
422 Error Something you sent could not be used, and error.fields names it. Nothing is ever silently clamped: limit=5000 is a refusal rather than a page of fifty, because a client that asked for five thousand and was handed fifty without being told has written a loop that never terminates.
429 Error Too many requests. retry_after is in the body and in the Retry-After header; wait that long, then try again. The cap that bites is per key.
500 Error Something broke on our side. The body never carries a trace, a file path or an exception name, not even on a deployment left in debug mode. Quote the X-Request-Id when you write to us.
503 Error The API is switched off right now, refused before anything is authenticated. retry_after is an hour. Back off and come back; do not treat it as a bad key and do not ask the person to re-enter anything.

GET /partner/connections/{externalId}

One shop, by your own identifier for it

The handle in the URL is the partner's own identifier for the merchant, never our integer id, and the lookup is always scoped to the calling partner. Another partner's shop answers exactly as an identifier nobody has ever sent, and so does an erased one.

What goes in the address

What goes in the address: GET /partner/connections/{externalId}
Name Where Required What it means
externalId path Yes Your own identifier for the merchant, the one you provisioned the shop with: a host or a slug, letters, digits, dot, dash and underscore, starting on a letter or a digit. It is stored and matched in LOWER CASE, so SHOP.MYSHOPIFY.COM and shop.myshopify.com are one shop on every database, and every answer prints the lower case form. THE SECOND FORM IS THE RETIRED ONE, redacted: followed by 64 hex characters, which is what an erased shop's identifier becomes and what the erasure endpoint answers with. Sending it back is how an erasure is idempotent, so the pattern admits both.

What you send

Nothing to send.

What comes back

What comes back: GET /partner/connections/{externalId}
Answer Shape What it means
200 ConnectionWithBusiness The connection and the business behind it.
401 Error No live key on this request. A missing header, a scheme that is not Bearer, a key we have never seen, a revoked key, an expired key, a key on a closed account and a partner key whose partner is gone all answer this same body: telling them apart would tell somebody holding a stolen key whether it is still worth trying. Ask the person to paste the key again; do not retry the same one.
403 Error The key is live and it may not do this: it does not carry the ability the endpoint asks for, or it is the wrong kind of key for the endpoint. Retrying will not help. A customer whose key cannot start checks has to mint a new one.
404 Error There is nothing here for this key. A business that does not exist and a business this key may not see are the same answer, deliberately: a different answer for the two would let a stranger walk the integers and learn how many customers we have.
405 Error That address does not answer this method. The router refuses this one before the route group runs, so the request id is minted by the error renderer instead of by the middleware; the header is there either way.
429 Error Too many requests. retry_after is in the body and in the Retry-After header; wait that long, then try again. The cap that bites is per key.
500 Error Something broke on our side. The body never carries a trace, a file path or an exception name, not even on a deployment left in debug mode. Quote the X-Request-Id when you write to us.
503 Error The API is switched off right now, refused before anything is authenticated. retry_after is an hour. Back off and come back; do not treat it as a bad key and do not ask the person to re-enter anything.

DELETE /partner/connections/{externalId}

The app was removed

The connection is marked uninstalled, the plan goes back to free and the weekly machine is switched off tonight. THE BUSINESS STAYS, and so does every receipt under it: a merchant who uninstalls on Friday and installs again on Monday has to find their own history, which is what the platform's grace window is for. Uninstalling twice is uninstalling once, and the second call writes nothing, not even a second event.

What goes in the address

What goes in the address: DELETE /partner/connections/{externalId}
Name Where Required What it means
externalId path Yes Your own identifier for the merchant, the one you provisioned the shop with: a host or a slug, letters, digits, dot, dash and underscore, starting on a letter or a digit. It is stored and matched in LOWER CASE, so SHOP.MYSHOPIFY.COM and shop.myshopify.com are one shop on every database, and every answer prints the lower case form. THE SECOND FORM IS THE RETIRED ONE, redacted: followed by 64 hex characters, which is what an erased shop's identifier becomes and what the erasure endpoint answers with. Sending it back is how an erasure is idempotent, so the pattern admits both.

What you send

Nothing to send.

What comes back

What comes back: DELETE /partner/connections/{externalId}
Answer Shape What it means
200 ConnectionOnly The connection, uninstalled.
401 Error No live key on this request. A missing header, a scheme that is not Bearer, a key we have never seen, a revoked key, an expired key, a key on a closed account and a partner key whose partner is gone all answer this same body: telling them apart would tell somebody holding a stolen key whether it is still worth trying. Ask the person to paste the key again; do not retry the same one.
403 Error The key is live and it may not do this: it does not carry the ability the endpoint asks for, or it is the wrong kind of key for the endpoint. Retrying will not help. A customer whose key cannot start checks has to mint a new one.
404 Error There is nothing here for this key. A business that does not exist and a business this key may not see are the same answer, deliberately: a different answer for the two would let a stranger walk the integers and learn how many customers we have.
405 Error That address does not answer this method. The router refuses this one before the route group runs, so the request id is minted by the error renderer instead of by the middleware; the header is there either way.
429 Error Too many requests. retry_after is in the body and in the Retry-After header; wait that long, then try again. The cap that bites is per key.
500 Error Something broke on our side. The body never carries a trace, a file path or an exception name, not even on a deployment left in debug mode. Quote the X-Request-Id when you write to us.
503 Error The API is switched off right now, refused before anything is authenticated. retry_after is an hour. Back off and come back; do not treat it as a bad key and do not ask the person to re-enter anything.

PUT /partner/connections/{externalId}/plan

Put this shop on a plan, billed by the platform

The merchant pays the platform and the platform pays us, so no invoice of ours is issued and no payment of ours is recorded. The plan is written onto the connection and onto the business, and the weekly machine reads it exactly as it reads a plan somebody bought here. Send free to take a paid plan away. THIS DOOR IS SHUT BY DEFAULT and answers 503 plan_grants_paused until we open it, which is the opposite of every other switch in this product: it moves money on somebody else's word, so an undefined switch and an unreadable switch store are both a refusal. A shop that is not installed is 409 connection_not_active.

What goes in the address

What goes in the address: PUT /partner/connections/{externalId}/plan
Name Where Required What it means
externalId path Yes Your own identifier for the merchant, the one you provisioned the shop with: a host or a slug, letters, digits, dot, dash and underscore, starting on a letter or a digit. It is stored and matched in LOWER CASE, so SHOP.MYSHOPIFY.COM and shop.myshopify.com are one shop on every database, and every answer prints the lower case form. THE SECOND FORM IS THE RETIRED ONE, redacted: followed by 64 hex characters, which is what an erased shop's identifier becomes and what the erasure endpoint answers with. Sending it back is how an erasure is idempotent, so the pattern admits both.

What you send

A body is required.

What you send: PUT /partner/connections/{externalId}/plan
Field Type Required What it means
plan string Yes The family. free takes a paid plan away and stops the weekly machine tonight.
reference string or null No Your own identifier for the charge, kept beside the grant.

What comes back

What comes back: PUT /partner/connections/{externalId}/plan
Answer Shape What it means
200 ConnectionWithBusiness The connection and the business, both on the new plan.
401 Error No live key on this request. A missing header, a scheme that is not Bearer, a key we have never seen, a revoked key, an expired key, a key on a closed account and a partner key whose partner is gone all answer this same body: telling them apart would tell somebody holding a stolen key whether it is still worth trying. Ask the person to paste the key again; do not retry the same one.
403 Error The key is live and it may not do this: it does not carry the ability the endpoint asks for, or it is the wrong kind of key for the endpoint. Retrying will not help. A customer whose key cannot start checks has to mint a new one.
404 Error There is nothing here for this key. A business that does not exist and a business this key may not see are the same answer, deliberately: a different answer for the two would let a stranger walk the integers and learn how many customers we have.
405 Error That address does not answer this method. The router refuses this one before the route group runs, so the request id is minted by the error renderer instead of by the middleware; the header is there either way.
409 Error connection_not_active: that shop is uninstalled or erased, so its plan cannot be changed.
413 Error The request body is over 16 KB, refused in front of the controller.
422 Error Something you sent could not be used, and error.fields names it. Nothing is ever silently clamped: limit=5000 is a refusal rather than a page of fifty, because a client that asked for five thousand and was handed fifty without being told has written a loop that never terminates.
429 Error Too many requests. retry_after is in the body and in the Retry-After header; wait that long, then try again. The cap that bites is per key.
500 Error Something broke on our side. The body never carries a trace, a file path or an exception name, not even on a deployment left in debug mode. Quote the X-Request-Id when you write to us.
503 Error api_paused (the whole API is off) or plan_grants_paused (this door is shut). Both carry retry_after and a Retry-After header.

POST /partner/connections/{externalId}/redact

Erase this merchant

WHAT GOES: the name becomes a fixed placeholder, the aliases empty, the website, the Google profile link and the free text category label null, the dedupe key random so nothing can land on the row again, the plan free and the business inactive. The addresses left on a free check, the readings taken of the website, the visits, the crawler hits and the log upload records are deleted outright, and every appearance loses the alias text that matched it. The mails we sent about that shop and any internal alert naming it go with them; our own funnel keeps its counts and loses their details; our staff audit trail keeps its rows and the shop's name and domain inside them become "redacted". The public results page of every check on that shop stops opening. The shop's identifier becomes a hash of itself and this connection, so a shop that installed again after an earlier erasure can be erased again on its own row, and the meta is cleared. WHAT STAYS: the runs and the answers. They are our samples of what a public assistant said when asked a public question, the prompt names a city and a trade rather than the shop, and throwing them away would destroy the count of every other business in that city and week. Nothing kept points back at the merchant once the matched alias is gone. Erasing twice is erasing once, but only if you send the hashed identifier from your own list: the original identifier no longer names anything here, so replaying the platform's webhook with it is a 404, which is the honest answer and the point of the erasure.

What goes in the address

What goes in the address: POST /partner/connections/{externalId}/redact
Name Where Required What it means
externalId path Yes Your own identifier for the merchant, the one you provisioned the shop with: a host or a slug, letters, digits, dot, dash and underscore, starting on a letter or a digit. It is stored and matched in LOWER CASE, so SHOP.MYSHOPIFY.COM and shop.myshopify.com are one shop on every database, and every answer prints the lower case form. THE SECOND FORM IS THE RETIRED ONE, redacted: followed by 64 hex characters, which is what an erased shop's identifier becomes and what the erasure endpoint answers with. Sending it back is how an erasure is idempotent, so the pattern admits both.

What you send

Nothing to send.

What comes back

What comes back: POST /partner/connections/{externalId}/redact
Answer Shape What it means
200 ConnectionOnly The connection, erased.
401 Error No live key on this request. A missing header, a scheme that is not Bearer, a key we have never seen, a revoked key, an expired key, a key on a closed account and a partner key whose partner is gone all answer this same body: telling them apart would tell somebody holding a stolen key whether it is still worth trying. Ask the person to paste the key again; do not retry the same one.
403 Error The key is live and it may not do this: it does not carry the ability the endpoint asks for, or it is the wrong kind of key for the endpoint. Retrying will not help. A customer whose key cannot start checks has to mint a new one.
404 Error There is nothing here for this key. A business that does not exist and a business this key may not see are the same answer, deliberately: a different answer for the two would let a stranger walk the integers and learn how many customers we have.
405 Error That address does not answer this method. The router refuses this one before the route group runs, so the request id is minted by the error renderer instead of by the middleware; the header is there either way.
429 Error Too many requests. retry_after is in the body and in the Retry-After header; wait that long, then try again. The cap that bites is per key.
500 Error Something broke on our side. The body never carries a trace, a file path or an exception name, not even on a deployment left in debug mode. Quote the X-Request-Id when you write to us.
503 Error The API is switched off right now, refused before anything is authenticated. retry_after is an hour. Back off and come back; do not treat it as a bad key and do not ask the person to re-enter anything.

The shapes, field by field

Every object the answers above are built from. A dotted name is a field inside a field, and a name ending in two brackets is a field of each item of a list.

Error

THE ONE SHAPE EVERY REFUSAL TAKES. Write one branch for a failure and read error.code; error.message is English on every request and is for a person reading a log, not for a business owner reading a page.

The shapes, field by field: Error
Field Type Always there What it means
error object Yes
error.code string Yes The closed list. Adding one is a change to this file as well as to the code, and a test holds the two together.
error.message string Yes One English sentence. Never a trace and never a file path.
error.fields object No On validation_failed only: the field name that could not be used, and what was wrong with it.
error.retry_after integer No On rate_limited, checks_limit_reached, api_paused and plan_grants_paused: seconds to wait. The same number is in the Retry-After header.
error.limit integer No On checks_limit_reached only: how many new checks a day this key may start (per key for a customer key, per shop for a partner key). A client prints this number; it never parses the message for it.
error.paused object No On business_paused only (402). This business has an open invoice, so its dashboard and every read about it are paused until the sum is paid. Nothing has been removed. The facts are the ones the panel's own paused page states, so a plugin can print a sentence its owner can act on: the sum, what it is made of, and where it is settled.
error.paused.amount string No The open sum, as a decimal string.
error.paused.currency string No
error.paused.vat string No How much of the sum is VAT, as a decimal string.
error.paused.invoice_count integer No
error.paused.invoices array of object No Every open invoice, oldest first, by number, issue date and gross. They are what the sum adds up to.
error.paused.paused_since string No The day the dashboard shut, or null.
error.paused.plan_ends_on string No The day the plan ends if the sum is still open then, or null.
error.paused.billing_url string No The page that settles it.

Count

A COUNT AND EVERYTHING THAT MUST TRAVEL WITH IT. No number of appearances leaves this API on its own. hits out of n, the Wilson bounds as proportions, the half width of that interval in whole points, and the sentence we would print ourselves. Printing hits without n is a misuse of this API; docs/api/README.md has the worked example.

The shapes, field by field: Count
Field Type Always there What it means
hits integer Yes How many answers named the business.
n integer Yes How many answers counted. A vendor error and an empty AI Overview are in no n anywhere: they are not checks a business failed.
low number Yes The lower Wilson bound, as a proportion between 0 and 1.
high number Yes The upper Wilson bound, as a proportion between 0 and 1.
points integer Yes Half the width of that interval, in whole points, which is the only form our own copy uses ("plus or minus 14 points").
phrase string Yes The sentence, in the reader's language, built by the same class every page and every mail of ours uses. An empty sample says "not checked yet" rather than a zero.

Business

One business, the same object in every endpoint that names one. website is the businesses.domain column under the name a client would call it. The site key is not here: it belongs to /snippet.

The shapes, field by field: Business
Field Type Always there What it means
id integer Yes
public_id string Yes The opaque identifier this business is addressed by. It is beside id, never instead of it: id stays an integer and is not going anywhere. Either spelling may be sent in the {id} slot of any path, and a new integration should store this one, because it is what the owner's own pages carry.
name string Yes
city string Yes
country string Yes ISO 3166-1 alpha-2, upper case.
language string Yes ISO 639-1, lower case.
category string Yes The pack slug this business is checked under, or other for a line of business we have no frame for.
category_label string or null Yes The free text the owner typed, when category is other.
website string or null Yes
plan string or null Yes
plan_since string or null Yes
created_at string Yes
frame_uncertain boolean Yes True when we could not place this line of business, so the questions are a general frame rather than a trade's own. A client printing blockers should say so, exactly as the receipt does.

BusinessList

The shapes, field by field: BusinessList
Field Type Always there What it means
businesses array of Business Yes
businesses[].id integer Yes
businesses[].public_id string Yes The opaque identifier this business is addressed by. It is beside id, never instead of it: id stays an integer and is not going anywhere. Either spelling may be sent in the {id} slot of any path, and a new integration should store this one, because it is what the owner's own pages carry.
businesses[].name string Yes
businesses[].city string Yes
businesses[].country string Yes ISO 3166-1 alpha-2, upper case.
businesses[].language string Yes ISO 639-1, lower case.
businesses[].category string Yes The pack slug this business is checked under, or other for a line of business we have no frame for.
businesses[].category_label string or null Yes The free text the owner typed, when category is other.
businesses[].website string or null Yes
businesses[].plan string or null Yes
businesses[].plan_since string or null Yes
businesses[].created_at string Yes
businesses[].frame_uncertain boolean Yes True when we could not place this line of business, so the questions are a general frame rather than a trade's own. A client printing blockers should say so, exactly as the receipt does.

BusinessSummary

The shapes, field by field: BusinessSummary
Field Type Always there What it means
business object Yes One business, the same object in every endpoint that names one. website is the businesses.domain column under the name a client would call it. The site key is not here: it belongs to /snippet.
business.id integer Yes
business.public_id string Yes The opaque identifier this business is addressed by. It is beside id, never instead of it: id stays an integer and is not going anywhere. Either spelling may be sent in the {id} slot of any path, and a new integration should store this one, because it is what the owner's own pages carry.
business.name string Yes
business.city string Yes
business.country string Yes ISO 3166-1 alpha-2, upper case.
business.language string Yes ISO 639-1, lower case.
business.category string Yes The pack slug this business is checked under, or other for a line of business we have no frame for.
business.category_label string or null Yes The free text the owner typed, when category is other.
business.website string or null Yes
business.plan string or null Yes
business.plan_since string or null Yes
business.created_at string Yes
business.frame_uncertain boolean Yes True when we could not place this line of business, so the questions are a general frame rather than a trade's own. A client printing blockers should say so, exactly as the receipt does.
summary object Yes
summary.kind string Yes weekly when a sampled week exists, free_check when the free check is all there is, none when there is nothing yet.
summary.headline Headline or null Yes Null when kind is none. No sample means no information, never a zero.
summary.running Running or null Yes Anything in flight for this business right now, of any kind.
summary.last_run_at string or null Yes When this business was last checked, whoever asked for it.

Summary

The shapes, field by field: Summary
Field Type Always there What it means
kind string Yes weekly when a sampled week exists, free_check when the free check is all there is, none when there is nothing yet.
headline Headline or null Yes Null when kind is none. No sample means no information, never a zero.
running Running or null Yes Anything in flight for this business right now, of any kind.
last_run_at string or null Yes When this business was last checked, whoever asked for it.

Headline

A plain value, described above.

Running

The shapes, field by field: Running
Field Type Always there What it means
run_id integer Yes
status string Yes
expected_answers integer Yes How many answers this run is planning to collect.
arrived integer Yes How many have landed, errors and empties included. Draw a progress bar against expected_answers with this, never with n.

WeekLedger

The shapes, field by field: WeekLedger
Field Type Always there What it means
weeks array of Week Yes One row per week, oldest first, free checks included.
rolling array of RollingWeek Yes The trailing four week window that ends at each sampled week. Free checks are never in here.

Week

A plain value, described above.

RollingWeek

A plain value, described above.

SliceReport

One window, cut one way. dimension says which cut it is, window_weeks says how wide the window was asked to be (0 is everything we hold) and weeks lists the ISO weeks that were actually sampled inside it, oldest first. A week nobody sampled is not a column: a gap stays a gap, because a week with no check and a week with no naming are different facts. It counts the same runs the headline counts, which is the weekly check and the baseline check the first payment bought, and nothing else: a free check somebody ran on the public checker is a different size of check and is in no figure here.

The shapes, field by field: SliceReport
Field Type Always there What it means
dimension string Yes
window_weeks integer Yes The window asked for, in weeks. 0 is every week we hold.
weeks array of string Yes The sampled ISO weeks this cut is drawn over, oldest first.
slices array of Slice Yes One row per assistant, in the roster order of the newest sampled run, or one row per question, fewest namings first. Neither order is a claim about anything, and there is no key here that says it is.
slices[].key string Yes The assistant's own name in our data, or the identifier of the question. Stable, and what a client joins two calls on.
slices[].label string Yes The assistant as a reader knows it, or the question word for word as we asked it that week. A template edited later does not rewrite what a past week was asked.
slices[].count integer Yes How many answers in this row named the business.
slices[].n integer Yes How many answers this row counted. Weeks the assistant or the question was not asked in are not in it, and neither is a vendor error.
slices[].low integer Yes The lower end of the usual range, as a whole count out of the same n. Our own sentence for a row reads "named in 27 of 90, usually between 19 and 36 of 90", and these are those two numbers.
slices[].high integer Yes The upper end of the usual range, as a whole count out of n.
slices[].grade string Yes How much weight this row will bear, read off the width of its range and the size of its n. too_thin is every n under ten and every empty sample. A row is printed with its sentence or it is not printed.
slices[].sentence string Yes The sentence we would print ourselves, in the reader's language. About our sample, never about the business: there is no verdict here and no adjective about anybody.
slices[].cells array of SliceCell Yes One per week in the report's weeks list, in the same order.
slices[].cells[].week string Yes The ISO week, as 2026-W36.
slices[].cells[].asked boolean Yes
slices[].cells[].count integer Yes
slices[].cells[].n integer Yes

Slice

One assistant, or one question, over the whole window. count out of n, the usual range as whole counts of that same n, and how much weight the row will bear. The range is ONE range over the window's totals, never one per week: a single week of a single assistant is nine or fifteen answers, and six ranges drawn across a row would each say almost nothing and together look like a trend.

The shapes, field by field: Slice
Field Type Always there What it means
key string Yes The assistant's own name in our data, or the identifier of the question. Stable, and what a client joins two calls on.
label string Yes The assistant as a reader knows it, or the question word for word as we asked it that week. A template edited later does not rewrite what a past week was asked.
count integer Yes How many answers in this row named the business.
n integer Yes How many answers this row counted. Weeks the assistant or the question was not asked in are not in it, and neither is a vendor error.
low integer Yes The lower end of the usual range, as a whole count out of the same n. Our own sentence for a row reads "named in 27 of 90, usually between 19 and 36 of 90", and these are those two numbers.
high integer Yes The upper end of the usual range, as a whole count out of n.
grade string Yes How much weight this row will bear, read off the width of its range and the size of its n. too_thin is every n under ten and every empty sample. A row is printed with its sentence or it is not printed.
sentence string Yes The sentence we would print ourselves, in the reader's language. About our sample, never about the business: there is no verdict here and no adjective about anybody.
cells array of SliceCell Yes One per week in the report's weeks list, in the same order.
cells[].week string Yes The ISO week, as 2026-W36.
cells[].asked boolean Yes
cells[].count integer Yes
cells[].n integer Yes

SliceCell

One week of one row. asked false means this assistant was not on that week's roster, or this question was not in that week's pack: the counts are zero and the week is in no total, because nobody asked. There is no range on a cell and no difference between two cells.

The shapes, field by field: SliceCell
Field Type Always there What it means
week string Yes The ISO week, as 2026-W36.
asked boolean Yes
count integer Yes
n integer Yes

Run

A plain value, described above.

RunPage

The shapes, field by field: RunPage
Field Type Always there What it means
runs array of Run Yes
next_before integer or null Yes Pass this back as ?before= for the next page. Null on the last one.

RunDetail

The shapes, field by field: RunDetail
Field Type Always there What it means
run Count or object Yes
questions array of Question Yes The questions this run asked, word for word as it asked them.
questions[].question_position integer Yes WHICH QUESTION OF THE PACK THIS IS, counting from one. It is the number of a question and nothing to do with a place in any list; the answer cards carry the same key, so the two lists join on it.
questions[].text string Yes
answers array of AnswerCard Yes
answers[].question_position integer Yes
answers[].question string Yes The question as it was asked, read off the answer rather than the pack.
answers[].assistant object Yes
answers[].assistant.id string Yes
answers[].assistant.label string Yes What to print, in the language that was asked for.
answers[].repeat integer Yes Which repeat of that question this answer is, counting from zero.
answers[].status string Yes ok is an answer with words in it. empty is an answer that arrived with nothing (an AI Overview that did not render). error is a vendor that failed.
answers[].verdict string Yes picked is a confirmed naming. possible is a match still waiting for a person, and it counts for nothing: it is in n and not in hits. not_picked is an answer that named somebody else. empty and error are in no n at all.
answers[].text string Yes The assistant's own words, plain, as they were stored.
answers[].excerpt string Yes The first 320 characters of the same text, cut on a word.
answers[].citations array of string Yes The URLs the assistant cited, as it sent them.
answers[].answered_at string or null Yes

Question

The shapes, field by field: Question
Field Type Always there What it means
question_position integer Yes WHICH QUESTION OF THE PACK THIS IS, counting from one. It is the number of a question and nothing to do with a place in any list; the answer cards carry the same key, so the two lists join on it.
text string Yes

AnswerPage

The shapes, field by field: AnswerPage
Field Type Always there What it means
run_id integer or null Yes Null when this business has nothing to show yet.
answers array of AnswerCard Yes
answers[].question_position integer Yes
answers[].question string Yes The question as it was asked, read off the answer rather than the pack.
answers[].assistant object Yes
answers[].assistant.id string Yes
answers[].assistant.label string Yes What to print, in the language that was asked for.
answers[].repeat integer Yes Which repeat of that question this answer is, counting from zero.
answers[].status string Yes ok is an answer with words in it. empty is an answer that arrived with nothing (an AI Overview that did not render). error is a vendor that failed.
answers[].verdict string Yes picked is a confirmed naming. possible is a match still waiting for a person, and it counts for nothing: it is in n and not in hits. not_picked is an answer that named somebody else. empty and error are in no n at all.
answers[].text string Yes The assistant's own words, plain, as they were stored.
answers[].excerpt string Yes The first 320 characters of the same text, cut on a word.
answers[].citations array of string Yes The URLs the assistant cited, as it sent them.
answers[].answered_at string or null Yes

AnswerCard

One answer and the verdict the panel gave it. No markup of ours is in here: the panel's own card carries a highlighted excerpt, and a client writing our markup into somebody's theme is not something we will ask for. The vendor's stored body never leaves the building either.

The shapes, field by field: AnswerCard
Field Type Always there What it means
question_position integer Yes
question string Yes The question as it was asked, read off the answer rather than the pack.
assistant object Yes
assistant.id string Yes
assistant.label string Yes What to print, in the language that was asked for.
repeat integer Yes Which repeat of that question this answer is, counting from zero.
status string Yes ok is an answer with words in it. empty is an answer that arrived with nothing (an AI Overview that did not render). error is a vendor that failed.
verdict string Yes picked is a confirmed naming. possible is a match still waiting for a person, and it counts for nothing: it is in n and not in hits. not_picked is an answer that named somebody else. empty and error are in no n at all.
text string Yes The assistant's own words, plain, as they were stored.
excerpt string Yes The first 320 characters of the same text, cut on a word.
citations array of string Yes The URLs the assistant cited, as it sent them.
answered_at string or null Yes

Assistant

The shapes, field by field: Assistant
Field Type Always there What it means
id string Yes
label string Yes What to print, in the language that was asked for.

BlockerReport

The shapes, field by field: BlockerReport
Field Type Always there What it means
run_id integer or null Yes
checked_at string or null Yes When this reading was taken. Null when the checks have not run: that is not a clean bill of health and must not be drawn as one.
blockers array of Blocker Yes The tasks, in the order an owner should read them.
blockers[].kind string Yes
blockers[].status string Yes
blockers[].tag string Yes Where the reading came from: measured is our own crawl of the site, vendor is a third party lookup, research is what the published research says about this check.
blockers[].title string Yes
blockers[].body string Yes Three sentences joined into one paragraph: what we found, why it matters, and what to do about it. Each of them ends in its own full stop, so a client can print the whole field as a paragraph. A client that printed only the title would be printing a scolding with no way out of it.
blockers[].state string Yes not_started is a failing check. in_progress is a warning. fixed is a check that was failing or warning at the FIRST reading we ever took of this business and is fine now, which is the owner's own work since we started watching.
rest array of Reading Yes Everything else we looked at and found nothing wrong with.
rest[].kind string Yes
rest[].status string Yes
rest[].tag string Yes Where the reading came from: measured is our own crawl of the site, vendor is a third party lookup, research is what the published research says about this check.
rest[].title string Yes
rest[].body string Yes Three sentences joined into one paragraph: what we found, why it matters, and what to do about it. Each of them ends in its own full stop, so a client can print the whole field as a paragraph. A client that printed only the title would be printing a scolding with no way out of it.

Reading

The shapes, field by field: Reading
Field Type Always there What it means
kind string Yes
status string Yes
tag string Yes Where the reading came from: measured is our own crawl of the site, vendor is a third party lookup, research is what the published research says about this check.
title string Yes
body string Yes Three sentences joined into one paragraph: what we found, why it matters, and what to do about it. Each of them ends in its own full stop, so a client can print the whole field as a paragraph. A client that printed only the title would be printing a scolding with no way out of it.

Blocker

A reading with a state on it. It is written out in full rather than as an allOf over Reading, and the reason is worth knowing: a JSON Schema allOf branch is applied to the WHOLE instance, so Reading's additionalProperties false refused the state key and every strict validator (ajv, openapi-core, Spectral) rejected every blockers answer this API gives.

The shapes, field by field: Blocker
Field Type Always there What it means
kind string Yes
status string Yes
tag string Yes Where the reading came from: measured is our own crawl of the site, vendor is a third party lookup, research is what the published research says about this check.
title string Yes
body string Yes Three sentences joined into one paragraph: what we found, why it matters, and what to do about it. Each of them ends in its own full stop, so a client can print the whole field as a paragraph. A client that printed only the title would be printing a scolding with no way out of it.
state string Yes not_started is a failing check. in_progress is a warning. fixed is a check that was failing or warning at the FIRST reading we ever took of this business and is fine now, which is the owner's own work since we started watching.

NamedLedger

The shapes, field by field: NamedLedger
Field Type Always there What it means
week string or null Yes The ISO week these counts are for, as 2026-W36.
n integer Yes How many answers this week's counts came out of.
compare_n integer Yes How many answers the four weeks ago column came out of.
rows array of NamedRow Yes
rows[].name string Yes
rows[].you boolean Yes True on the row that is this business.
rows[].this_week integer Yes
rows[].four_weeks integer Yes
rows[].change integer or null Yes this_week minus four_weeks, or null when there is nothing to compare.

NamedRow

A name and how many answers named it. There is no ordinal key in this object and there never will be: an assistant holds no league table for anybody to be third of, so we do not publish a place in one.

The shapes, field by field: NamedRow
Field Type Always there What it means
name string Yes
you boolean Yes True on the row that is this business.
this_week integer Yes
four_weeks integer Yes
change integer or null Yes this_week minus four_weeks, or null when there is nothing to compare.

ReportList

The shapes, field by field: ReportList
Field Type Always there What it means
reports array of ClosedMonth Yes The months that have closed, newest first. A month still running is not here at all.
reports[].period string Yes The month, as four digits, a hyphen and two. It is the id of this report.
reports[].hits integer or null Yes How many of the month's answers named the business.
reports[].n integer or null Yes How many answers that count came out of.
reports[].low integer or null Yes The low end of the usual range, as a whole count of the same n, not a proportion and not a share of a hundred. Null with high on a month of fewer than two sampled weeks: a band drawn around a single reading is not something we publish.
reports[].high integer or null Yes The high end of the same range, in the same whole counts.
reports[].sampled_weeks integer Yes How many Mondays of the month were actually sampled.
reports[].closed_at string or null Yes When the figures were frozen.
reports[].amended_at string or null Yes When the month was corrected, if it ever was. An amended report keeps its first figures and prints both, so a client holding the original can see that something moved and when.

ClosedMonth

One month, measured once on the day it closed and never counted again. hits and n are null together on a month nobody sampled, because no sample is not a measurement of nothing.

The shapes, field by field: ClosedMonth
Field Type Always there What it means
period string Yes The month, as four digits, a hyphen and two. It is the id of this report.
hits integer or null Yes How many of the month's answers named the business.
n integer or null Yes How many answers that count came out of.
low integer or null Yes The low end of the usual range, as a whole count of the same n, not a proportion and not a share of a hundred. Null with high on a month of fewer than two sampled weeks: a band drawn around a single reading is not something we publish.
high integer or null Yes The high end of the same range, in the same whole counts.
sampled_weeks integer Yes How many Mondays of the month were actually sampled.
closed_at string or null Yes When the figures were frozen.
amended_at string or null Yes When the month was corrected, if it ever was. An amended report keeps its first figures and prints both, so a client holding the original can see that something moved and when.

MonthReport

A closed month, whole, exactly as it was frozen. THIS OBJECT IS NOT CLOSED, and that is deliberate: the document belongs to the month report rather than to this contract, and a schema that refused a field the document grew would make a client's validator reject a customer's own September. The keys below are the ones every report carries and they will not be removed; read them by name and ignore anything else you meet. The nested objects are the sections the paper prints, and each one is documented in docs/api/README.md rather than pinned key by key here. TWO KEYS DEPEND ON THE KEY ASKING: pages and pages_published are present only for a key carrying the pages ability, and are absent rather than empty for every other key and for every partner key, since an empty list would read as a claim that no page was published.

The shapes, field by field: MonthReport
Field Type Always there What it means
period string Yes The month, as four digits, a hyphen and two.
period_start string No
period_end string No
business object No The name, town, country, trade and plan as they were that month.
hits integer or null Yes How many of the month's answers named the business.
n integer or null Yes How many answers that count came out of.
low integer or null Yes The low end of the usual range, as a whole count of the same n.
high integer or null Yes The high end of the same range, in the same whole counts.
sampled_weeks integer Yes
mondays array of string No Every Monday of the month, sampled or not.
weeks array of object Yes One entry a sampled Monday, with its own count, its n, its cut by assistant and the clock the run ran between.
engines object No The assistants of that month with their totals, in the enum's order.
questions integer No How many questions the month actually asked, counted rather than assumed.
assistants integer No
repeats integer No How many times each question went to each assistant.
answers_a_week integer or null No
excerpts object No Two answers quoted word for word, the first sampled Monday and the last. The excerpt carries one mark tag around the matched name, because it is the document's own excerpt; strip it if your surface does not want it.
blockers object No What was in the way that month, and what stopped being.
visits object No People who reached a page carrying the snippet, by assistant, and what each assistant's crawler fetched.
named object or null No The businesses named beside this one. Null on a plan that does not carry the four week list.
previous object or null No The month before, and whether the two usual ranges overlap. Two ranges that overlap are the usual swing; two that do not are the one case where we say a month was different. Never a cause.
reviewed integer No How many matches a person confirmed that month.
held integer No How many matches are still waiting for a person. They are in n and in no count.
closed_at string or null Yes When the figures were frozen.

Receipts

The shapes, field by field: Receipts
Field Type Always there What it means
days integer Yes The window, in days. Thirty.
installed boolean Yes Whether anything has ever arrived. A snippet on a page nobody arrives at from an assistant sends nothing, by design, so this is the honest thing we can say about it.
since string or null Yes The first visit we ever recorded for this business.
visits object Yes
visits.total integer Yes
visits.by_assistant array of AssistantVisits Yes One row per assistant that was actually seen. No rows of zeroes.
visits.by_assistant[].assistant string Yes
visits.by_assistant[].label string Yes
visits.by_assistant[].count integer Yes
visits.by_day array of DayVisits Yes Thirty days, oldest first, every one of them printed.
visits.by_day[].date string Yes
visits.by_day[].count integer Yes

Visits

The shapes, field by field: Visits
Field Type Always there What it means
total integer Yes
by_assistant array of AssistantVisits Yes One row per assistant that was actually seen. No rows of zeroes.
by_assistant[].assistant string Yes
by_assistant[].label string Yes
by_assistant[].count integer Yes
by_day array of DayVisits Yes Thirty days, oldest first, every one of them printed.
by_day[].date string Yes
by_day[].count integer Yes

AssistantVisits

The shapes, field by field: AssistantVisits
Field Type Always there What it means
assistant string Yes
label string Yes
count integer Yes

DayVisits

The shapes, field by field: DayVisits
Field Type Always there What it means
date string Yes
count integer Yes

Snippet

The shapes, field by field: Snippet
Field Type Always there What it means
site_key string Yes The name this business's receipts are filed under. It sits in a script tag on the owner's own pages, so it is not a secret, but it is behind the same key check as everything else here.
script_url string Yes The script tag's src. Built from this deployment's own routes.
beacon_url string Yes Where the script posts an arrival.
enabled boolean Yes Whether we are recording visits at all right now. False means a client should say counting is paused rather than show an owner a snippet that is quietly doing nothing.
instruction string Yes One sentence to print beside the switch, in the owner's language.

PagesQueue

The shapes, field by field: PagesQueue
Field Type Always there What it means
publish array of PageToPublish Yes Pages approved and not yet reported as up. Empty is the ordinary answer.
publish[].page string Yes The page's own id, which is what a report names.
publish[].business integer Yes The business this page belongs to, so a client can refuse an item that is not the one it asked about.
publish[].approval string Yes The approvals row that authorised it. It is on the customer's own ledger, and everything we later write about this page hangs off it.
publish[].title string Yes The title as the customer approved it, or edited it.
publish[].slug string Yes A suggestion for the address. WordPress makes it unique on its own side, and nothing here depends on the one you end up with.
publish[].html string Yes The text of the approvals row itself: h2, h3, p, ul, li and links to the business's own website, with the disclosure line as the last paragraph. Put it through your platform's own sanitiser anyway.
publish[].approved_at string or null Yes When the customer approved it.
publish[].post_type_hint string Yes What we would choose if the site had no preference. The site owner's own setting wins; this is a word, not an instruction.
unpublish array of PageToUnpublish Yes Pages whose owner pressed Undo and which this site has not reported taking down yet.
unpublish[].page string Yes The page's own id, which is what a report names.
unpublish[].business integer Yes The business this page belongs to.
unpublish[].approval string Yes The approvals row of the UNDO, not of the publication. What the site says about taking a page down belongs under the click that asked for it.
unpublish[].wp_post_id integer or null Yes The post id you reported when you put it up, handed back so you do not have to keep a second list. Null when we never got one.

PageToPublish

The shapes, field by field: PageToPublish
Field Type Always there What it means
page string Yes The page's own id, which is what a report names.
business integer Yes The business this page belongs to, so a client can refuse an item that is not the one it asked about.
approval string Yes The approvals row that authorised it. It is on the customer's own ledger, and everything we later write about this page hangs off it.
title string Yes The title as the customer approved it, or edited it.
slug string Yes A suggestion for the address. WordPress makes it unique on its own side, and nothing here depends on the one you end up with.
html string Yes The text of the approvals row itself: h2, h3, p, ul, li and links to the business's own website, with the disclosure line as the last paragraph. Put it through your platform's own sanitiser anyway.
approved_at string or null Yes When the customer approved it.
post_type_hint string Yes What we would choose if the site had no preference. The site owner's own setting wins; this is a word, not an instruction.

PageToUnpublish

The shapes, field by field: PageToUnpublish
Field Type Always there What it means
page string Yes The page's own id, which is what a report names.
business integer Yes The business this page belongs to.
approval string Yes The approvals row of the UNDO, not of the publication. What the site says about taking a page down belongs under the click that asked for it.
wp_post_id integer or null Yes The post id you reported when you put it up, handed back so you do not have to keep a second list. Null when we never got one.

PageReport

The shapes, field by field: PageReport
Field Type Always there What it means
action string Yes Which of the two lists this item came off.
outcome string Yes done means the site did it. failed means it could not, nothing moves, and the item stays on the queue for the next sync.
post_id integer or null No The id of the post you made, or the one you updated.
url string or null No Where the page is. Required on a done publish, because it is what we print to the customer and what we then read back ourselves. It has to be an https address on the business's own website.
message string or null No Anything the site wants to add, above all on a failure. It is stored with the rest of the body, word for word.

PageReportRecorded

The shapes, field by field: PageReportRecorded
Field Type Always there What it means
page string Yes The page the report was filed against.
action string Yes
outcome string Yes
state string Yes What the page is now. approved is waiting for this site, published is up, unpublished is down again. A report that arrived twice answers the same word as the first one.

PartnersList

The shapes, field by field: PartnersList
Field Type Always there What it means
enabled boolean Yes Whether this business has a partners page at all. False with an empty list is the ordinary answer for one that never opted in, and it is what to print nothing on.
rel string Yes What the owner chose for the links on their own site. followed means no rel attribute at all on the anchor; nofollow means exactly rel="nofollow". Links on picksyou.com's own record are always nofollow, whatever this says.
record_url string Yes The record of this list on picksyou.com, which exists whether or not the site shows anything.
site_page_url string or null Yes The page of the business's own site that shows the list, as that site last told us, or null when none is registered.
disclosure string Yes One sentence that says what the page is. Print it above the list, on every render, unchanged. A page without it is not a partners page, and our read-back looks for it.
method_url string Yes The one link of ours the page carries, explaining how partner links work. Give it rel="nofollow".
partners array of PartnerEntry Yes One entry per live pair, in the order the pairs were made.
partners[].name string Yes The partner's name, as the link's text.
partners[].url string Yes The partner's own website, as the link's address.
partners[].trade string Yes The partner's trade, in the same word every question of ours uses.
partners[].place string Yes The partner's town, or the word online.
partners[].note string Yes THIS business's own sentence about that partner, never the partner's about them. It is their own words and it is what makes the entry a recommendation rather than a list of links. Print it as text; it carries no markup and none is allowed in it.
partners[].since string Yes The month the pair was made, as 2026-09.

PartnerEntry

The shapes, field by field: PartnerEntry
Field Type Always there What it means
name string Yes The partner's name, as the link's text.
url string Yes The partner's own website, as the link's address.
trade string Yes The partner's trade, in the same word every question of ours uses.
place string Yes The partner's town, or the word online.
note string Yes THIS business's own sentence about that partner, never the partner's about them. It is their own words and it is what makes the entry a recommendation rather than a list of links. Print it as text; it carries no markup and none is allowed in it.
since string Yes The month the pair was made, as 2026-09.

PartnersPageReport

The shapes, field by field: PartnersPageReport
Field Type Always there What it means
url string or null Yes The permalink of the page showing the list, over https and on the business's own website, or null to clear it. The field itself is required either way.
post_id integer or null No The id of the post on your side, if your platform has one.

PartnersPageRegistered

The shapes, field by field: PartnersPageRegistered
Field Type Always there What it means
site_page_url string or null Yes The address we now hold, or null after a clearing.
post_id integer or null Yes The post id we now hold, or null.
registered_at string or null Yes When it was registered, or null after a clearing.

CheckAccepted

The shapes, field by field: CheckAccepted
Field Type Always there What it means
outcome string Yes started is a new run. retried is a failed run pushed back onto the queue, keeping the answers it already had. reused is a fresh run that already existed, and nothing was created or spent. queued is a run dated for tomorrow because today's budget is committed.
run object Yes
run.id integer Yes
run.public_id string Yes The opaque identifier of the run that was started, beside id and never instead of it.
run.kind string Yes The same word the read endpoints use: a free check is free_check, a paid week is weekly, the first paid week is baseline.
run.status string Yes
run.scheduled_for string or null Yes The day the run belongs to. Tomorrow, on the queued outcome.
run.expected_answers integer or null Yes Null until the fan out is planned, seconds later. Null rather than zero, because a zero reads to a progress bar as a run with nothing in it.
run.poll_url string Yes The run endpoint to ask again in a few seconds. It spells the two ids as integers, which every version of this API answers.
run.receipt_url string or null Yes The public results page for this run, which a client may put in front of a business owner as a link.

CheckRun

The shapes, field by field: CheckRun
Field Type Always there What it means
id integer Yes
public_id string Yes The opaque identifier of the run that was started, beside id and never instead of it.
kind string Yes The same word the read endpoints use: a free check is free_check, a paid week is weekly, the first paid week is baseline.
status string Yes
scheduled_for string or null Yes The day the run belongs to. Tomorrow, on the queued outcome.
expected_answers integer or null Yes Null until the fan out is planned, seconds later. Null rather than zero, because a zero reads to a progress bar as a run with nothing in it.
poll_url string Yes The run endpoint to ask again in a few seconds. It spells the two ids as integers, which every version of this API answers.
receipt_url string or null Yes The public results page for this run, which a client may put in front of a business owner as a link.

ConnectionRequest

The shapes, field by field: ConnectionRequest
Field Type Always there What it means
external_id string Yes Your own identifier for the merchant. The handle every later call uses.
name string Yes The business name as the merchant writes it.
website string or null No With or without a scheme; it is reduced to a bare host on our side, exactly as a typed one is.
city string Yes
country string Yes ISO 3166-1 alpha-2, and it must be a country the AI Overview vendor can search in: a country we cannot search is a country we cannot answer about.
language string Yes The language the assistants are asked in. English only since 2026-09-05; any other value is refused with 422 and error.code validation_failed.
category string Yes A pack slug, or other. Free text lines of business are checked in a general frame and say so through frame_uncertain.
category_label string or null No Required when category is other. Letters, spaces and hyphens, up to 40.
meta object or null No Anything you want to remember about this shop: a plan name on your side, a theme, an install source. At most 2 KB once encoded. Nothing personal: it survives an erasure.

PlanRequest

The shapes, field by field: PlanRequest
Field Type Always there What it means
plan string Yes The family. free takes a paid plan away and stops the weekly machine tonight.
reference string or null No Your own identifier for the charge, kept beside the grant.

Connection

The shapes, field by field: Connection
Field Type Always there What it means
id integer Yes
external_id string Yes Your identifier for the merchant, or, after an erasure, a hash of it prefixed with "redacted:".
status string Yes
plan ConnectionPlan or null Yes Null when the shop is on free, rather than an object full of nulls: "this shop is not paying" is one fact and reads as one.
installed_at string or null Yes
uninstalled_at string or null Yes
redacted_at string or null Yes
business_id integer Yes
business_public_id string Yes The opaque identifier of the same business, beside business_id and never instead of it. Either spelling may be sent in the {id} slot of a business path.

ConnectionPlan

The shapes, field by field: ConnectionPlan
Field Type Always there What it means
family string or null Yes
code string Yes The family's active price code, which is what the weekly machine reads, whether the money came through us or through a platform.
reference string or null Yes
granted_at string or null Yes

ConnectionPage

The shapes, field by field: ConnectionPage
Field Type Always there What it means
connections array of Connection Yes
connections[].id integer Yes
connections[].external_id string Yes Your identifier for the merchant, or, after an erasure, a hash of it prefixed with "redacted:".
connections[].status string Yes
connections[].plan ConnectionPlan or null Yes Null when the shop is on free, rather than an object full of nulls: "this shop is not paying" is one fact and reads as one.
connections[].installed_at string or null Yes
connections[].uninstalled_at string or null Yes
connections[].redacted_at string or null Yes
connections[].business_id integer Yes
connections[].business_public_id string Yes The opaque identifier of the same business, beside business_id and never instead of it. Either spelling may be sent in the {id} slot of a business path.
next_before integer or null Yes

ConnectionWithBusiness

The shapes, field by field: ConnectionWithBusiness
Field Type Always there What it means
connection object Yes
connection.id integer Yes
connection.external_id string Yes Your identifier for the merchant, or, after an erasure, a hash of it prefixed with "redacted:".
connection.status string Yes
connection.plan ConnectionPlan or null Yes Null when the shop is on free, rather than an object full of nulls: "this shop is not paying" is one fact and reads as one.
connection.installed_at string or null Yes
connection.uninstalled_at string or null Yes
connection.redacted_at string or null Yes
connection.business_id integer Yes
connection.business_public_id string Yes The opaque identifier of the same business, beside business_id and never instead of it. Either spelling may be sent in the {id} slot of a business path.
business object Yes One business, the same object in every endpoint that names one. website is the businesses.domain column under the name a client would call it. The site key is not here: it belongs to /snippet.
business.id integer Yes
business.public_id string Yes The opaque identifier this business is addressed by. It is beside id, never instead of it: id stays an integer and is not going anywhere. Either spelling may be sent in the {id} slot of any path, and a new integration should store this one, because it is what the owner's own pages carry.
business.name string Yes
business.city string Yes
business.country string Yes ISO 3166-1 alpha-2, upper case.
business.language string Yes ISO 639-1, lower case.
business.category string Yes The pack slug this business is checked under, or other for a line of business we have no frame for.
business.category_label string or null Yes The free text the owner typed, when category is other.
business.website string or null Yes
business.plan string or null Yes
business.plan_since string or null Yes
business.created_at string Yes
business.frame_uncertain boolean Yes True when we could not place this line of business, so the questions are a general frame rather than a trade's own. A client printing blockers should say so, exactly as the receipt does.

ConnectionOnly

The shapes, field by field: ConnectionOnly
Field Type Always there What it means
connection object Yes
connection.id integer Yes
connection.external_id string Yes Your identifier for the merchant, or, after an erasure, a hash of it prefixed with "redacted:".
connection.status string Yes
connection.plan ConnectionPlan or null Yes Null when the shop is on free, rather than an object full of nulls: "this shop is not paying" is one fact and reads as one.
connection.installed_at string or null Yes
connection.uninstalled_at string or null Yes
connection.redacted_at string or null Yes
connection.business_id integer Yes
connection.business_public_id string Yes The opaque identifier of the same business, beside business_id and never instead of it. Either spelling may be sent in the {id} slot of a business path.

Me

A customer key answers one shape and a partner key the other.

A plain value, described above.

MeCustomer

The shapes, field by field: MeCustomer
Field Type Always there What it means
kind string Yes
key object Yes The key as its holder sees it. Never the token and never its hash.
key.name string Yes What the person who minted it called it.
key.prefix string Yes The first twelve characters, which is enough to tell two keys apart.
key.abilities array of string Yes
key.scope object Yes
key.scope.business_id integer or null Yes The one business this key may read, or null for every business the account holds.
key.created_at string Yes
key.expires_at string or null Yes
key.last_used_at string or null Yes Written at most once every five minutes, so it means "used today" rather than "used at this second".
account Account or null Yes
businesses array of MeBusiness Yes Every business this key may read, in id order. One row when the key is scoped to one site.
businesses[].id integer Yes
businesses[].name string Yes
businesses[].city string Yes
businesses[].country string Yes
businesses[].language string Yes
businesses[].plan string or null Yes

MePartner

The shapes, field by field: MePartner
Field Type Always there What it means
kind string Yes
key object Yes The key as its holder sees it. Never the token and never its hash.
key.name string Yes What the person who minted it called it.
key.prefix string Yes The first twelve characters, which is enough to tell two keys apart.
key.abilities array of string Yes
key.scope object Yes
key.scope.business_id integer or null Yes The one business this key may read, or null for every business the account holds.
key.created_at string Yes
key.expires_at string or null Yes
key.last_used_at string or null Yes Written at most once every five minutes, so it means "used today" rather than "used at this second".
partner PartnerIdentity or null Yes
connections object Yes How many shops this partner has with us, by state. All three keys are always here, zeroes included, so a dashboard on its first day is not branching on a missing field.
connections.active integer Yes
connections.uninstalled integer Yes
connections.redacted integer Yes

KeySummary

The key as its holder sees it. Never the token and never its hash.

The shapes, field by field: KeySummary
Field Type Always there What it means
name string Yes What the person who minted it called it.
prefix string Yes The first twelve characters, which is enough to tell two keys apart.
abilities array of string Yes
scope object Yes
scope.business_id integer or null Yes The one business this key may read, or null for every business the account holds.
created_at string Yes
expires_at string or null Yes
last_used_at string or null Yes Written at most once every five minutes, so it means "used today" rather than "used at this second".

KeyScope

The shapes, field by field: KeyScope
Field Type Always there What it means
business_id integer or null Yes The one business this key may read, or null for every business the account holds.

Account

The account holding this key. No address: that belongs to the account and not to the machine holding a key to it.

The shapes, field by field: Account
Field Type Always there What it means
name string Yes
locale string Yes The language this account reads. English only since 2026-09-05.

MeBusiness

The short business row /me lists. The full object, with the category and the dates, is on /businesses.

The shapes, field by field: MeBusiness
Field Type Always there What it means
id integer Yes
name string Yes
city string Yes
country string Yes
language string Yes
plan string or null Yes

PartnerIdentity

The shapes, field by field: PartnerIdentity
Field Type Always there What it means
name string Yes
slug string Yes

ConnectionCounts

How many shops this partner has with us, by state. All three keys are always here, zeroes included, so a dashboard on its first day is not branching on a missing field.

The shapes, field by field: ConnectionCounts
Field Type Always there What it means
active integer Yes
uninstalled integer Yes
redacted integer Yes

The MCP tools

The same numbers, in the shape a language model can call. Streamable HTTP, POST only, and it takes the same key in the same header. Nothing on it writes anything: no tool changes a website, an account or a subscription, and there is nowhere in the code for one to say it does.

Address
https://picksyou.com/mcp
Protocol revision
2025-06-18
The MCP tools
Tool What it answers with
list_businesses Every business this API key may read, with its id, name, city, country and plan. Call this first: every other tool takes one of these ids.
get_weeks Week by week, how often AI assistants named this business (count) out of how many answers were sampled (n), with the usual range around that count. `rolling` is the four week window every headline is read from; a single week is never the headline.
get_slices The sampled window cut one row per assistant (dimension=engine) or one row per question (dimension=prompt), each row with its count, its n, its usual range and a plain sentence saying how much weight the row will bear.
get_answers Up to 10 cards from the newest sampled run: the question asked, the assistant that answered, the verdict (picked means it named this business, possible means a match nobody has confirmed yet and it counts for nothing), when it answered and the hosts it cited. The answers themselves are not carried here: the business owner reads them in their dashboard, on a proof link, or through GET /api/v1/businesses/{id}/answers with their own key.
get_blockers The findings that are keeping this business out of AI answers, worst first, each with the evidence behind it and the fix. Advice only: nothing in this product changes a customer site without their own recorded approval.
get_named Growth plan only. The other businesses assistants named in the same answers, with how often each was named. Counts only: an AI answer does not put businesses in order. A business that is not on Growth answers not found.
get_receipts How many visitors arrived at this business website from an AI assistant, by assistant and by landing page. A count of arrivals: not a rate, not a share of traffic and never a revenue figure.
get_reports Every month that has closed for this business, newest first, each with the count, the n and the two ends of its usual range. A month still running is not in this list.
get_report One closed month for this business, as it was frozen at the close: the count, the n, the usual range, the assistants and the questions. Periods come from get_reports.

The document itself

The contract this page is generated from, as a file your own tooling can read. It holds no key and no credential of ours.

https://picksyou.com/api/openapi.yaml

The written guide, from getting a key to every endpoint, travels with the source; ask us for it and we send it.

If anything on this page does not match what the API actually did, that is a bug on our side and we want to hear about it. Quote the request id from the answer you got.

How the counts are measured Write to us