# Citypicked > Citypicked gets you new reviews after every visit, by SMS, email or WhatsApp, automatically if you like. So new customers find you on Google and in AI answers. Google and AI recommend businesses with plenty of fresh reviews. Citypicked gets them for you after every visit, automatically. Criticism comes to you first. Citypicked connects to Zapier, Make, n8n and your checkout or booking system, and sends customers to Google and 117 other review sites. Pricing: Basic from $59, Pro from $119 with 50 SMS and Business from $239 with 400 SMS a month, 20% off yearly. Try free. The site is in English; also in German at https://citypicked.com/de, French at https://citypicked.com/fr, Spanish at https://citypicked.com/es, Italian at https://citypicked.com/it, Portuguese (Portugal) at https://citypicked.com/pt, Dutch at https://citypicked.com/nl, Arabic (Gulf) at https://citypicked.com/ar, Japanese at https://citypicked.com/ja. Sign-up and the app: https://app.citypicked.com/signup. ## Product - [Home](https://citypicked.com/en): Citypicked gets you new reviews after every visit, by SMS, email or WhatsApp, automatically if you like. So new customers find you on Google and in AI answers. - [Features](https://citypicked.com/en/features): More stars, less work. - [Requests](https://citypicked.com/en/features/requests): Ask your customers for a review by SMS, email, WhatsApp or QR code. One at a time or a whole list, with reminders and fixed sending hours. - [Review page](https://citypicked.com/en/features/review-page): Your own review page in your colors, with a star filter: good ratings go on to Google and 117 other review sites, complaints come to you privately. - [QR codes](https://citypicked.com/en/features/qr-codes): QR codes for the counter, table and receipt: table card, poster, sticker and business card as print-ready PDFs with your logo. Every code is counted on its own. - [Private feedback](https://citypicked.com/en/features/feedback): Customers who give few stars write to you privately, not in public. An email right away, replies from Citypicked, status and notes for your team. Never published. - [Automation](https://citypicked.com/en/features/automation): Review requests by themselves after every appointment: one automation link for Zapier, Make, n8n, Shopify Flow, Calendly and your checkout system. With guides for each. - [Pricing](https://citypicked.com/en/pricing): Basic from $59, Pro from $119 with 50 SMS and Business from $239 with 400 SMS a month, 20% off yearly. Try free. - [Integrations](https://citypicked.com/en/integrations): Citypicked connects to Zapier, Make, n8n and your checkout or booking system, and sends customers to Google and 117 other review sites. - [AI agents](https://citypicked.com/en/agents): Connect Claude or ChatGPT to Citypicked. One sentence, and your customers get a review request by SMS or email. - [Your Google review link. With a QR code, free.](https://citypicked.com/en/tools/google-review-link): Free: turn your business's Google Maps link into the link that opens the review window directly, plus a QR code to print. - [Contact](https://citypicked.com/en/contact): Write to us: questions about Citypicked, your account or pricing. We reply by email. ## Developers - [Citypicked for developers](https://citypicked.com/en/developers): Send review requests, read ratings and private feedback, and get your numbers. From your own software, an automation tool or an AI assistant. Markdown: https://citypicked.com/en/developers.md - [Quickstart](https://citypicked.com/en/developers/quickstart): Four steps from nothing to your first review request. You need a Citypicked account and a terminal. Markdown: https://citypicked.com/en/developers/quickstart.md - [Authentication](https://citypicked.com/en/developers/authentication): API keys for your own code and scripts, sign-in with Citypicked for AI assistants. Both act as a person on your team, never more. Markdown: https://citypicked.com/en/developers/authentication.md - [API reference](https://citypicked.com/en/developers/api): Every endpoint of the REST API with its parameters, an example call, an example answer and its errors. Generated from the OpenAPI document. Markdown: https://citypicked.com/en/developers/api.md - [Webhooks](https://citypicked.com/en/developers/webhooks): Get each rating, click and piece of private feedback at your own URL within seconds, signed so you know it came from Citypicked. Markdown: https://citypicked.com/en/developers/webhooks.md - [MCP for AI assistants](https://citypicked.com/en/developers/mcp): Connect Citypicked to Claude, ChatGPT, Claude Code or Cursor. Then ask in plain language, and your assistant sends review requests and reads your results. Markdown: https://citypicked.com/en/developers/mcp.md - [CLI](https://citypicked.com/en/developers/cli): Citypicked from the terminal. Send review requests, get review links and QR codes, and read feedback and numbers, by hand or in scripts. Markdown: https://citypicked.com/en/developers/cli.md - [Rate limits and errors](https://citypicked.com/en/developers/errors): How many calls you can make, what an error looks like and what each one means. Markdown: https://citypicked.com/en/developers/errors.md - [Changelog](https://citypicked.com/en/developers/changelog): What changed in the API, newest first. Changes within v1 only add things; nothing you use stops working. Markdown: https://citypicked.com/en/developers/changelog.md - [OpenAPI document](https://api.citypicked.com/v1/openapi.json): the REST API as OpenAPI 3.1, every call and event ## Industries - [From hair salons to dental practices.](https://citypicked.com/en/industries) - [Doctors' offices](https://citypicked.com/en/industries/medical): Patient reviews for your practice, as discreet as your exam room - [Dental practices](https://citypicked.com/en/industries/dental): Reviews from patients who just left your chair smiling - [Orthodontists](https://citypicked.com/en/industries/orthodontics): Reviews on the day the braces come off - [Physical therapy clinics](https://citypicked.com/en/industries/physio): Ask for the review at the last session, not the first - [Occupational and speech therapy practices](https://citypicked.com/en/industries/therapy): Reviews from the parents who come along every week - [Therapists and counselors](https://citypicked.com/en/industries/psychotherapy): Client reviews without pressure, only when it feels right - [Naturopaths and holistic practitioners](https://citypicked.com/en/industries/naturopaths): Reviews for your holistic practice, as personal as your sessions - [Opticians and eyewear stores](https://citypicked.com/en/industries/opticians): When the new glasses fit, ask for a review - [Hearing aid centers](https://citypicked.com/en/industries/hearing): The end of the trial period is the moment to ask for a review - [Pharmacies](https://citypicked.com/en/industries/pharmacy): Reviews that show the care behind your pharmacy counter - [Home care agencies](https://citypicked.com/en/industries/homecare): Reviews from the families who trust you with their parents - [Veterinary clinics](https://citypicked.com/en/industries/vets): Reviews from pet owners when their pet is back on its feet - [Hair salons](https://citypicked.com/en/industries/salons): Turn the mirror moment into a Google review - [Barbershops](https://citypicked.com/en/industries/barbers): Google reviews straight from the barber chair - [Skin care and facial studios](https://citypicked.com/en/industries/beauty): Reviews that arrive when the skin shows the results - [Nail salons](https://citypicked.com/en/industries/nails): Google reviews while the nails are drying - [Massage therapists](https://citypicked.com/en/industries/massage): Reviews that don't break the calm after a massage - [Day spas and wellness](https://citypicked.com/en/industries/spa): Reviews from spa guests, sent once they are home - [Tattoo and piercing studios](https://citypicked.com/en/industries/tattoo): Tattoo reviews that wait until the ink has healed - [Restaurants](https://citypicked.com/en/industries/restaurants): More reviews for your restaurant, right from the table - [Cafés and bakeries](https://citypicked.com/en/industries/cafes): Reviews for your bakery café, no customer list needed - [Bars and pubs](https://citypicked.com/en/industries/bars): Reviews for your bar, collected between rounds - [Takeout and delivery restaurants](https://citypicked.com/en/industries/delivery): Reviews for your takeout spot, straight from the box - [Caterers](https://citypicked.com/en/industries/catering): Reviews for your catering from the clients who booked you - [Butchers and delis](https://citypicked.com/en/industries/butchers): Reviews for your butcher shop, from the counter to the holiday roast - [Hotels](https://citypicked.com/en/industries/hotels): Guest reviews for your hotel, once the suitcase is unpacked - [Vacation rentals and B&Bs](https://citypicked.com/en/industries/rentals): Reviews from the guests who booked with you directly - [Campgrounds and RV parks](https://citypicked.com/en/industries/camping): Campground reviews, asked at the office on the way out - [Escape rooms](https://citypicked.com/en/industries/escape-rooms): Escape room reviews while the team is still celebrating - [Bowling, go-karts and family fun centers](https://citypicked.com/en/industries/leisure): Reviews from the lanes, the track and the party room - [Event venues](https://citypicked.com/en/industries/venues): Reviews from the couples and planners who booked your venue - [Photographers](https://citypicked.com/en/industries/photographers): Reviews that arrive along with the finished gallery - [Contractors and trades](https://citypicked.com/en/industries/trades): Reviews for your trade business, without extra paperwork - [Electricians](https://citypicked.com/en/industries/electricians): Ask for the review when the lights come back on - [Plumbing and HVAC](https://citypicked.com/en/industries/plumbing): Reviews from the emergency call to the finished bathroom - [Roofers](https://citypicked.com/en/industries/roofers): Every new roof deserves a review - [Painters](https://citypicked.com/en/industries/painters): Freshly painted, freshly reviewed - [Carpenters and cabinet makers](https://citypicked.com/en/industries/carpenters): Reviews for the furniture your customers waited months for - [Landscapers](https://citypicked.com/en/industries/garden): Reviews for your landscaping, from new beds to fall cleanup - [Cleaning services](https://citypicked.com/en/industries/cleaning): Reviews for your cleaning business, without asking regulars every week - [Moving companies](https://citypicked.com/en/industries/movers): Reviews the morning after the move, before your customer is gone - [Locksmiths](https://citypicked.com/en/industries/locksmiths): Honest reviews for an honest locksmith - [Phone repair shops](https://citypicked.com/en/industries/phone-repair): The phone works again. That is the moment to ask for a review. - [Car dealers and auto repair shops](https://citypicked.com/en/industries/auto): Reviews after every delivery and every service visit - [Tire shops](https://citypicked.com/en/industries/tires): Google reviews from every set of tires you put on - [Car washes and detailers](https://citypicked.com/en/industries/car-wash): Reviews while the paint is still shining - [Bike shops](https://citypicked.com/en/industries/bikes): Bike shop reviews that arrive after the first ride - [Driving schools](https://citypicked.com/en/industries/driving): Reviews from students who just passed their road test - [Gyms](https://citypicked.com/en/industries/fitness): Reviews from the members who actually show up - [Yoga and Pilates studios](https://citypicked.com/en/industries/yoga): Reviews for your studio, without breaking the calm after class - [Martial arts schools](https://citypicked.com/en/industries/martial-arts): New belt, proud parents, an honest review - [Dance studios](https://citypicked.com/en/industries/dance): Reviews from couples who nailed their first dance - [Music schools](https://citypicked.com/en/industries/music): Your music school on Google, rated by parents after the recital - [Tutoring](https://citypicked.com/en/industries/tutoring): When the grades improve, ask parents for a review - [Language schools](https://citypicked.com/en/industries/language-schools): Reviews on the last day of class, before students move on - [Dog trainers and groomers](https://citypicked.com/en/industries/dog-training): Google reviews for trainers and groomers, right at pick-up - [Accountants and tax preparers](https://citypicked.com/en/industries/tax): Ask clients for reviews when the refund comes in - [Law firms](https://citypicked.com/en/industries/lawyers): Client reviews for your firm, only after the matter is closed - [Real estate agents](https://citypicked.com/en/industries/realestate): From closing day straight to a review on Google - [Insurance agents](https://citypicked.com/en/industries/insurance): Reviews from the clients you helped through a claim - [Property managers](https://citypicked.com/en/industries/property-management): Reviews for your property management, not just from angry tenants - [Local shops and retailers](https://citypicked.com/en/industries/retail): Good advice in your shop, showing up in your Google reviews - [Florists](https://citypicked.com/en/industries/florists): Reviews for your flower shop, asked for with care - [Online stores](https://citypicked.com/en/industries/online-shops): Store reviews after the package arrives, not at checkout - [Apps](https://citypicked.com/en/industries/apps): App reviews from users who just reached a goal ## Review sites - [Google](https://citypicked.com/en/integrations/google): More Google reviews for your business. - [Tripadvisor](https://citypicked.com/en/integrations/tripadvisor): More Tripadvisor reviews for your restaurant or hotel. - [Jameda](https://citypicked.com/en/integrations/jameda): More Jameda reviews for your practice. - [Trustpilot](https://citypicked.com/en/integrations/trustpilot): More Trustpilot reviews for your company. - [Facebook](https://citypicked.com/en/integrations/facebook): More recommendations on your Facebook page. - [OpenTable](https://citypicked.com/en/integrations/opentable): More reviews for your restaurant, beyond OpenTable. - [Booking.com](https://citypicked.com/en/integrations/booking): More reviews for your property, beyond Booking.com. - [Airbnb](https://citypicked.com/en/integrations/airbnb): More reviews for your vacation rental, beyond Airbnb. - [HolidayCheck](https://citypicked.com/en/integrations/holidaycheck): More HolidayCheck reviews for your hotel. - [TheFork](https://citypicked.com/en/integrations/thefork): More reviews for your restaurant, on top of TheFork. - [Treatwell](https://citypicked.com/en/integrations/treatwell): More reviews for your salon, beyond Treatwell. - [Fresha](https://citypicked.com/en/integrations/fresha): Fresha asks on its own. Citypicked gets the Google reviews. - [Booksy](https://citypicked.com/en/integrations/booksy): More reviews for your barbershop, beyond Booksy. - [Planity](https://citypicked.com/en/integrations/planity): Planity asks online bookers. Citypicked asks everyone else. - [Bokadirekt](https://citypicked.com/en/integrations/bokadirekt): Bokadirekt asks after the visit. Citypicked asks for Google. - [Vagaro](https://citypicked.com/en/integrations/vagaro): More Vagaro reviews for your business. - [StyleSeat](https://citypicked.com/en/integrations/styleseat): More reviews for your work, beyond StyleSeat. - [Mindbody](https://citypicked.com/en/integrations/mindbody): More reviews for your studio, beyond Mindbody. - [Rover](https://citypicked.com/en/integrations/rover): More reviews for your pet care, beyond Rover. - [The Knot](https://citypicked.com/en/integrations/the-knot): More The Knot reviews for your wedding business. - [WeddingWire](https://citypicked.com/en/integrations/weddingwire): More WeddingWire reviews, soon after the big day. - [Hitched](https://citypicked.com/en/integrations/hitched): More Hitched reviews from the couples who booked you. - [Bodas.net](https://citypicked.com/en/integrations/bodas-net): More Bodas.net reviews, requested by WhatsApp. - [Matrimonio.com](https://citypicked.com/en/integrations/matrimonio-com): More Matrimonio.com reviews for your wedding venue. - [Mariages.net](https://citypicked.com/en/integrations/mariages-net): More Mariages.net reviews for your photo studio. - [Casamentos.pt](https://citypicked.com/en/integrations/casamentos-pt): More Casamentos.pt reviews for your catering business. - [Easy Weddings](https://citypicked.com/en/integrations/easy-weddings): More Easy Weddings reviews from your couples. - [DocFinder](https://citypicked.com/en/integrations/docfinder): More DocFinder reviews for your practice. - [Healthgrades](https://citypicked.com/en/integrations/healthgrades): More Healthgrades reviews for your practice. - [Zocdoc](https://citypicked.com/en/integrations/zocdoc): Your Zocdoc profile on your review page. - [RateMDs](https://citypicked.com/en/integrations/ratemds): More RateMDs reviews for your practice. - [MioDottore](https://citypicked.com/en/integrations/miodottore): More MioDottore reviews for your practice in Italy. - [Doctoralia](https://citypicked.com/en/integrations/doctoralia): More Doctoralia reviews for your practice. - [ZnanyLekarz](https://citypicked.com/en/integrations/znanylekarz): More ZnanyLekarz reviews for your practice in Poland. - [ZorgkaartNederland](https://citypicked.com/en/integrations/zorgkaart-nederland): More ZorgkaartNederland reviews for your practice. - [Legelisten](https://citypicked.com/en/integrations/legelisten): More Legelisten reviews for your practice in Norway. - [Top Doctors](https://citypicked.com/en/integrations/top-doctors): More Top Doctors reviews for your practice. - [iWantGreatCare](https://citypicked.com/en/integrations/iwantgreatcare): More iWantGreatCare reviews for your UK practice. - [Vitals](https://citypicked.com/en/integrations/vitals): More Vitals reviews for your practice. - [WhatClinic](https://citypicked.com/en/integrations/whatclinic): More WhatClinic reviews for your clinic. - [RealSelf](https://citypicked.com/en/integrations/realself): More RealSelf reviews for your practice. - [Doctify](https://citypicked.com/en/integrations/doctify): Your Doctify profile, one tap away. - [carehome.co.uk](https://citypicked.com/en/integrations/carehome-co-uk): More carehome.co.uk reviews for your care home. - [homecare.co.uk](https://citypicked.com/en/integrations/homecare-co-uk): More homecare.co.uk reviews for your home care agency. - [Care.com](https://citypicked.com/en/integrations/care-com): More Care.com reviews for your care business. - [Avvo](https://citypicked.com/en/integrations/avvo): More Avvo reviews for your law firm. - [Martindale-Hubbell](https://citypicked.com/en/integrations/martindale): More Martindale-Hubbell client reviews for your firm. - [Lawyers.com](https://citypicked.com/en/integrations/lawyers-com): More Lawyers.com reviews for your law firm. - [ReviewSolicitors](https://citypicked.com/en/integrations/review-solicitors): More ReviewSolicitors reviews for your firm. - [anwalt.de](https://citypicked.com/en/integrations/anwalt-de): More anwalt.de reviews for your law firm. - [MyHammer](https://citypicked.com/en/integrations/myhammer): More reviews for your MyHammer jobs. - [CHECK24](https://citypicked.com/en/integrations/check24): More reviews for your CHECK24 jobs. - [Angi](https://citypicked.com/en/integrations/angi): More Angi reviews for your home service business. - [Houzz](https://citypicked.com/en/integrations/houzz): More Houzz reviews for your projects. - [Thumbtack](https://citypicked.com/en/integrations/thumbtack): More Thumbtack reviews for your business. - [Checkatrade](https://citypicked.com/en/integrations/checkatrade): More Checkatrade reviews for your trade business. - [TrustATrader](https://citypicked.com/en/integrations/trustatrader): More TrustATrader reviews for your trade business. - [Which? Trusted Traders](https://citypicked.com/en/integrations/which-trusted-traders): More reviews on your Which? Trusted Traders profile. - [HomeStars](https://citypicked.com/en/integrations/homestars): More HomeStars reviews for your business. - [hipages](https://citypicked.com/en/integrations/hipages): Your hipages profile, one tap away. - [Bark](https://citypicked.com/en/integrations/bark): More Bark reviews for your services. - [Travaux.com](https://citypicked.com/en/integrations/travaux-com): More reviews for your Travaux.com jobs. - [Instapro](https://citypicked.com/en/integrations/instapro): More reviews for your Instapro jobs. - [Habitissimo](https://citypicked.com/en/integrations/habitissimo): More Habitissimo reviews for your business. - [Fixando](https://citypicked.com/en/integrations/fixando): More reviews for your Fixando jobs. - [Trustoo](https://citypicked.com/en/integrations/trustoo): More reviews on your Trustoo page. - [Trustlocal](https://citypicked.com/en/integrations/trustlocal): More reviews on your Trustlocal page. - [Reco](https://citypicked.com/en/integrations/reco): More Reco reviews for your company. - [Mittanbud](https://citypicked.com/en/integrations/mittanbud): More reviews for your Mittanbud jobs. - [Servicefinder](https://citypicked.com/en/integrations/servicefinder): More reviews for your Servicefinder jobs. - [Oferteo](https://citypicked.com/en/integrations/oferteo): More Oferteo reviews for your company. - [ServiceSeeking](https://citypicked.com/en/integrations/serviceseeking): More reviews for your ServiceSeeking jobs. - [NoCowboys](https://citypicked.com/en/integrations/nocowboys): More NoCowboys ratings for your business. - [TrustedPros](https://citypicked.com/en/integrations/trustedpros-ca): More TrustedPros reviews for your business. - [MyBuilder](https://citypicked.com/en/integrations/mybuilder): Show your customers your MyBuilder profile. - [Builderscrack](https://citypicked.com/en/integrations/builderscrack): More Builderscrack reviews for your business. - [Werkspot](https://citypicked.com/en/integrations/werkspot): More reviews for your Werkspot jobs. - [Zaask](https://citypicked.com/en/integrations/zaask): Your Zaask profile, one tap away. - [Yelp](https://citypicked.com/en/integrations/yelp): Yelp: get found without asking for reviews. - [ProvenExpert](https://citypicked.com/en/integrations/provenexpert): More ProvenExpert reviews for your business. - [Trusted Shops](https://citypicked.com/en/integrations/trustedshops): Trusted Shops and Citypicked: how they fit together. - [Das Örtliche](https://citypicked.com/en/integrations/dasoertliche): More Das Örtliche reviews for your business. - [Gelbe Seiten](https://citypicked.com/en/integrations/gelbeseiten): More Gelbe Seiten reviews for your business. - [BBB](https://citypicked.com/en/integrations/bbb): More BBB reviews for your business. - [Nextdoor](https://citypicked.com/en/integrations/nextdoor): More Nextdoor recommendations from your neighborhood. - [Yell](https://citypicked.com/en/integrations/yell): More Yell reviews for your business. - [Yellowpages.com](https://citypicked.com/en/integrations/yellowpages-us): More reviews on Yellowpages.com. - [ProductReview.com.au](https://citypicked.com/en/integrations/productreview): More ProductReview.com.au reviews for your business. - [PagesJaunes](https://citypicked.com/en/integrations/pagesjaunes): More PagesJaunes reviews for your business. - [11880.com](https://citypicked.com/en/integrations/11880): More 11880.com reviews for your business. - [golocal](https://citypicked.com/en/integrations/golocal): More golocal reviews, seen across many directories. - [HEROLD](https://citypicked.com/en/integrations/herold): More HEROLD reviews for your business. - [search.ch](https://citypicked.com/en/integrations/search-ch): More search.ch reviews for your business. - [Páginas Amarillas](https://citypicked.com/en/integrations/paginas-amarillas): More Páginas Amarillas reviews for your business. - [Panorama Firm](https://citypicked.com/en/integrations/panorama-firm): More Panorama Firm reviews for your business. - [YellowPages.ca](https://citypicked.com/en/integrations/yellowpages-ca): More YellowPages.ca reviews for your business. - [TrueLocal](https://citypicked.com/en/integrations/truelocal): More TrueLocal reviews for your business. - [KennstDuEinen](https://citypicked.com/en/integrations/kennstdueinen): More KennstDuEinen reviews for your business. - [REVIEWS.io](https://citypicked.com/en/integrations/reviews-io): REVIEWS.io reviews, by REVIEWS.io's rules. - [SmartCustomer](https://citypicked.com/en/integrations/smartcustomer): SmartCustomer and Citypicked: what's allowed. - [ConsumerAffairs](https://citypicked.com/en/integrations/consumeraffairs): More ConsumerAffairs reviews for your company. - [Manta](https://citypicked.com/en/integrations/manta): More Manta reviews for your small business. - [FreeIndex](https://citypicked.com/en/integrations/freeindex): More FreeIndex reviews for your business. - [Word of Mouth](https://citypicked.com/en/integrations/womo): More Word of Mouth reviews for your business. - [Feedback Company](https://citypicked.com/en/integrations/feedback-company): More Feedback Company reviews for your business. - [Kiyoh](https://citypicked.com/en/integrations/kiyoh): More Kiyoh reviews for your business. - [Klantenvertellen](https://citypicked.com/en/integrations/klantenvertellen): More Klantenvertellen reviews for your business. - [ShopVote](https://citypicked.com/en/integrations/shopvote): More ShopVote reviews, asked in person. - [Feefo](https://citypicked.com/en/integrations/feefo): Show your Feefo reviews, collect Google reviews. - [Avis Vérifiés](https://citypicked.com/en/integrations/avis-verifies): Show your Avis Vérifiés reviews, collect Google reviews. - [DealerRater](https://citypicked.com/en/integrations/dealerrater): More DealerRater reviews for your dealership. - [Cars.com](https://citypicked.com/en/integrations/cars-com): More Cars.com reviews for your dealership. - [CarGurus](https://citypicked.com/en/integrations/cargurus): CarGurus invites its own reviewers. Here's where Citypicked fits. - [Edmunds](https://citypicked.com/en/integrations/edmunds): More Edmunds reviews for your dealership. - [Auto Trader](https://citypicked.com/en/integrations/autotrader-uk): More Auto Trader reviews for your dealership. - [Autobutler](https://citypicked.com/en/integrations/autobutler): Autobutler reviews come from bookings. Here's where Citypicked fits. - [AutoScout24](https://citypicked.com/en/integrations/autoscout24): AutoScout24 invites its own reviewers. Here's where Citypicked fits. - [mobile.de](https://citypicked.com/en/integrations/mobile-de): mobile.de invites its own reviewers. Here's where Citypicked fits. - [iDGarages](https://citypicked.com/en/integrations/idgarages): iDGarages reviews come from bookings. Here's where Citypicked fits. - [Zillow](https://citypicked.com/en/integrations/zillow): More Zillow reviews for you as an agent. - [Realtor.com](https://citypicked.com/en/integrations/realtor-com): Realtor.com reviews start in your account. Here's where Citypicked fits. - [RateMyAgent](https://citypicked.com/en/integrations/ratemyagent): More RateMyAgent reviews for you as an agent. - [allAgents](https://citypicked.com/en/integrations/allagents): More allAgents reviews for your agency. - [MeilleursAgents](https://citypicked.com/en/integrations/meilleursagents): More MeilleursAgents reviews for your agency. - [Immodvisor](https://citypicked.com/en/integrations/immodvisor): More Immodvisor reviews for your agency. - [G2](https://citypicked.com/en/integrations/g2): More G2 reviews for your software. - [Capterra](https://citypicked.com/en/integrations/capterra): More Capterra reviews for your software. - [Clutch](https://citypicked.com/en/integrations/clutch): More Clutch reviews for your agency. - [Google Play](https://citypicked.com/en/integrations/google-play): More Google Play reviews for your app. - [App Store](https://citypicked.com/en/integrations/app-store): More App Store ratings for your app. ## Help - [Help](https://help.citypicked.com/en): Guides for everything you do with Citypicked. Step by step, with pictures from the app. - [Set up Citypicked in 5 minutes](https://help.citypicked.com/en/get-started) - [How to find your Google review link](https://help.citypicked.com/en/google-review-link) - [Send your first request](https://help.citypicked.com/en/first-request) - [Your overview at a glance](https://help.citypicked.com/en/overview) - [How to get more Google reviews](https://help.citypicked.com/en/more-google-reviews) - [Reminders: a friendly follow-up](https://help.citypicked.com/en/reminders) - [The best moment to ask](https://help.citypicked.com/en/best-time) - [An unhappy customer: what to do](https://help.citypicked.com/en/bad-review) - [Read and answer private feedback](https://help.citypicked.com/en/private-feedback) - [Set up notifications](https://help.citypicked.com/en/notifications) - [SMS, email, WhatsApp or QR code: which fits?](https://help.citypicked.com/en/channels) - [Send requests by WhatsApp](https://help.citypicked.com/en/whatsapp) - [Edit your message and sender name](https://help.citypicked.com/en/message-text) - [Put up your QR code](https://help.citypicked.com/en/qr-code) - [Sending times and delay](https://help.citypicked.com/en/sending) - [What a request's status means](https://help.citypicked.com/en/request-status) - [When customers reply to the SMS](https://help.citypicked.com/en/sms-replies) - [How the star filter works](https://help.citypicked.com/en/star-filter) - [Design your review page: logo, color, texts](https://help.citypicked.com/en/review-page-design) - [Add and sort your review sites](https://help.citypicked.com/en/review-sites) - [Share your link: website, Instagram, email signature](https://help.citypicked.com/en/page-link) - [Show your reviews on your website](https://help.citypicked.com/en/website-widget) - [Add the widget to WordPress](https://help.citypicked.com/en/widget-wordpress) - [Add the widget to Wix](https://help.citypicked.com/en/widget-wix) - [Add the widget to Squarespace](https://help.citypicked.com/en/widget-squarespace) - [Add the widget to Shopify](https://help.citypicked.com/en/widget-shopify) - [Add the widget to Webflow](https://help.citypicked.com/en/widget-webflow) - [Automatic after every appointment](https://help.citypicked.com/en/automation) - [Upload a customer list (CSV)](https://help.citypicked.com/en/csv-list) - [Connect Zapier to Citypicked](https://help.citypicked.com/en/automation-zapier) - [Connect Make to Citypicked](https://help.citypicked.com/en/automation-make) - [Connect n8n to Citypicked](https://help.citypicked.com/en/automation-n8n) - [Connect Shopify Flow to Citypicked](https://help.citypicked.com/en/automation-shopify-flow) - [Connect Calendly to Citypicked](https://help.citypicked.com/en/automation-calendly) - [Connect Google Calendar to Citypicked](https://help.citypicked.com/en/automation-google-calendar) - [Connect Google Sheets to Citypicked](https://help.citypicked.com/en/automation-google-sheets) - [Read your analytics: from request to review](https://help.citypicked.com/en/analytics) - [The weekly report by email](https://help.citypicked.com/en/weekly-report) - [Invite your team and choose roles](https://help.citypicked.com/en/team) - [Run several locations](https://help.citypicked.com/en/locations) - [Your free trial: how it works](https://help.citypicked.com/en/trial) - [Change your plan: Basic, Pro and Business](https://help.citypicked.com/en/plans) - [SMS credits](https://help.citypicked.com/en/credits) - [Top up credits automatically](https://help.citypicked.com/en/auto-top-up) - [Invoices and payment method](https://help.citypicked.com/en/invoices) - [Cancel your subscription](https://help.citypicked.com/en/cancel) - [Your account: profile, data export and deleting](https://help.citypicked.com/en/account) - [Log in without a password](https://help.citypicked.com/en/login) - [Permission and unsubscribing](https://help.citypicked.com/en/consent) - [Delete a customer's data (privacy)](https://help.citypicked.com/en/delete-contact) - [The SMS doesn't arrive](https://help.citypicked.com/en/sms-not-arriving) - [The email doesn't arrive](https://help.citypicked.com/en/email-not-arriving) - [The link doesn't open](https://help.citypicked.com/en/link-not-opening) - [The Google link goes to the wrong place](https://help.citypicked.com/en/wrong-review-link) - [Automatic requests don't go out](https://help.citypicked.com/en/automation-not-working) ## Optional - [Privacy Policy](https://citypicked.com/en/legal/privacy): How Citypicked handles personal data: who is responsible, what we process and why, legal bases, recipients, retention, cookies, your rights and a notice for California. - [Terms of Service](https://citypicked.com/en/legal/terms): Citypicked Terms of Service for businesses: license, trial and automatic renewal, fees and SMS credits, your duties when sending, customer data, disclaimers, liability, arbitration and Wyoming law. - [Full text of the help articles](https://citypicked.com/llms-full.txt) ## Help articles in full ## Getting started in 5 minutes ### Set up Citypicked in 5 minutes Source: https://help.citypicked.com/en/get-started You want more reviews but have no time for tech? Setup walks you through it one question at a time. At the end your review page is live and you have sent yourself a test request. 1. Create your account: On the website, click “Try free”, enter your email address in the window and click “Get started for free”. You don't need a password: to log in later, we simply email you a link. 2. Your business: Enter your business name, your industry and your country. Then we ask a few quick questions about your goals, for example how many reviews you have today. We use them to suggest texts and review sites. 3. Pick your review sites: Choose where customers should review you. For Google, type your business name and town and pick your business from the list. Happy customers then go straight to Google's review window. If it isn't listed, paste your link from Google Maps instead. 4. Page and messages: Upload your logo, pick a color and a headline. The preview shows right away what your customers see. The templates for SMS, email and WhatsApp already fit; change only what you like. 5. Try it yourself: Send yourself the first request by email with “Send test request”. Open it and pick a star, just like your customers will. Citypicked ticks off every step: email sent, review page opened, rating given. 6. First customers and QR code: If you like, enter a few customers who visited recently and pick the places for your QR code. You can also skip both with “Later”. 7. Start your trial: Choose a plan and add your card. You pay nothing today. If you cancel before the trial ends, it costs nothing. 8. Carry on in the overview: Then you are in the overview. At the top it shows what is still missing, for example more review sites or your QR code, and right there you can send your first real request. Tip: Use your own email address for the test. That way you see exactly what your customers get. The test doesn't count in Overview or Analytics. ### How to find your Google review link Source: https://help.citypicked.com/en/google-review-link Without the right link, happy customers never reach Google. Here is how to find it in a minute and add it to Citypicked. 1. Open your business in Google Maps, on your phone or computer. 2. Tap “Share” and then “Copy link”. The link usually starts with maps.app.goo.gl. 3. Paste the link into Citypicked: during setup in the review sites step, later under Review page, Review sites, Google. Citypicked checks it right away and shows “Link works” or “Please check” next to it. 4. Do you manage your Business Profile yourself? Then the link from “Ask for reviews” in your profile works too. It usually starts with g.page/r/. Tip: Test the link once yourself: open your review page, give 5 stars and tap Google. Your business should open on Google, ideally right in the review window. Good to know: Our website has a free tool for this: “Create your Google review link and QR code”. It turns your Maps link into the link that opens the review window directly. ### Send your first request Source: https://help.citypicked.com/en/first-request A short message after the visit is enough. Here is how to send your first review request by SMS, email or WhatsApp in under a minute. 1. Click “Requests” on the left. At the top, choose SMS, email or WhatsApp. 2. Type your customer's name and mobile number (or email address). With “Add row” you ask several customers at once, up to 50. 3. Under “When”, choose whether the request goes out as soon as possible or later, for example “In 2 hours” when the customer should get home first. 4. Check “I have permission to contact these customers.” and click “Send”. Tip: Send the first one to yourself to see exactly what customers get. The preview next to the form shows you beforehand what the message looks like. #### When it goes out Requests only go out during your sending hours. Outside them, the request waits for the next allowed time, and the form tells you when beforehand. Want it to go out right away? In the list, click “Actions” on the request and then “Send now”. It goes out at once, even outside your sending hours. Good to know: SMS need credits; email and WhatsApp are included in your plan. What a text costs is shown below the form before you send. ### Your overview at a glance Source: https://help.citypicked.com/en/overview The overview is your home screen. It shows the last 30 days, what to do next and what is happening with your requests right now. #### What you see there - “Get more out of it”: the next steps, while something is still missing. Add Google, add more review sites, ask your first real customer and put up your QR code. Each step ticks itself off. - Four numbers for the last 30 days: Requests sent, Opened, Star ratings and Went to review sites. “All numbers in Analytics” takes you further. - “Send a quick request”: enter a name and a mobile number or email, check the box, done. For several customers or WhatsApp, use “Several, or via WhatsApp”. - “Recent activity”: what is happening with your requests, newest first. For example “Anna gave 5 stars”. Good to know: Star ratings and Went to review sites count every customer, also via QR code and direct link. The test request from setup doesn't count. Tip: When there is new private feedback, it shows at the very top with “Read and reply”. Best answer it the same day. ## Get more Google reviews ### How to get more Google reviews Source: https://help.citypicked.com/en/more-google-reviews Your customers are happy, but hardly anything new shows up on Google? With these steps you ask every one of them at the right moment, without it taking your time. 1. Link Google directly: Add Google under Review page, Review sites. With the link from Google Maps your customers reach you on Google in one tap. If you have only one review site, Citypicked sends happy customers straight there. 2. Ask every customer: Send a request after every visit, not just to your regulars. By SMS, email or WhatsApp, depending on what you have from your customer. 3. At the right moment: Ask the same day while everything is fresh. Under “When” you can pick “In 2 hours”, for example, and your sending hours make sure nobody gets a message at night. 4. Remind once, kindly: Switch on the reminder. Anyone who hasn't given stars yet gets a short second message after a few days. Anyone who already rated gets none. 5. In person with a QR code: Put your QR code on the counter, on tables or with the receipt. That way you also reach customers whose number you don't have. 6. Automatically after every appointment: If you use a booking or checkout system, it can tell Citypicked after every appointment. Then every request goes out by itself. Watch out: Never offer a reward for a review and never write reviews yourself. A friendly request is allowed; rewards and fake reviews are not. Good to know: Nobody can promise you a certain spot on Google or a mention in AI answers, and neither can we. What you can do: collect real, recent reviews regularly. ### Reminders: a friendly follow-up Source: https://help.citypicked.com/en/reminders Some customers mean to leave a review and then forget. A short reminder after a few days brings them back, and anyone who already rated gets none. 1. Open Requests and click “Edit message”. Under “Reminders” you see Reminder 1 and Reminder 2. 2. Each reminder has its own switch. Reminder 1 is on from the start and comes after 3 days; switch on Reminder 2 if you like. Under “Time after the request” pick 1 to 14 days. Reminder 2 must come after Reminder 1. 3. Under “When to remind”, choose “Only if no stars given yet” or “Only if the link was not opened”. 4. You edit each reminder's text right below it, separately for SMS and email. The timing and the condition apply to both. Everything saves right away. #### Good to know - Reminders only go out during your sending hours, like the request itself. - When a customer gives stars, writes to you privately, goes on to a review site or unsubscribes, their reminders stop. - With WhatsApp you send the messages yourself, so there are no automatic reminders there. - SMS reminders come with Pro and Business. On the other plans, reminders go out by email only and use no credits. - An SMS reminder costs credits like any SMS. Before you send, the form tells you the most reminders can cost later. ### The best moment to ask Source: https://help.citypicked.com/en/best-time Right after the visit the memory is fresh and the good feeling strongest. Here is how to make your requests arrive just then, and never in the middle of the night. 1. When sending: “When”: When sending, choose under “When”: “As soon as possible”, “In 1 hour”, “In 2 hours”, “In 4 hours” or “Tomorrow morning”. If the customer is still in your shop, “In 2 hours” gives them time to get home. 2. Sending hours: Under Settings, Sending you set when requests may go out at all, for example your opening hours. Anything outside them waits until they begin again. 3. Automatically after appointments: If the request comes from your booking or checkout system, set the “Wait after the appointment” at your automation link: from “Right away” to “1 day”. Tip: Ask the same day rather than a week later. And ask everyone only once: “Don't ask twice” makes sure a customer gets no second request for a while. ## Handle a bad review ### An unhappy customer: what to do Source: https://help.citypicked.com/en/bad-review Criticism hurts, especially in public. With Citypicked you usually hear it privately first, and you can respond before anyone writes a bad review. 1. Criticism comes to you: With the star filter, customers with a low rating don't see the review sites but a private form. Their message only goes to you. 2. Get notified right away: Under Settings, Notifications, switch on “Private feedback right away” and “Low rating”. Then you get an email as soon as someone is unhappy. 3. Answer calmly and soon: Open Feedback and click the message. Under “Reply by email” there is already a friendly draft with the customer's name. Adjust it and click “Send reply”. Or call, if the customer left a number. 4. Keep track of what you do: Set the status to “In progress” and later to “Done”. Under “Notes”, write down what you agreed with the customer. Only your team sees notes. Tip: Thank them for being honest, apologize for what went wrong and say what you will change. Justifying yourself makes a reply feel cold. #### And if the bad review is already on Google? Citypicked can't change or delete reviews on Google or other review sites. Reply there publicly, briefly and factually, as described above. Anyone who reads your reviews later sees your reply too. What helps most: many new, genuine reviews from happy customers. Then a single bad one is just one among many. ### Read and answer private feedback Source: https://help.citypicked.com/en/private-feedback Everything customers write to you privately lands under Feedback. Here is how to keep track, reply by email and note what is done. 1. Click “Feedback” on the left. The number next to it shows how much new feedback there is. At the top you filter by All, New, In progress and Done. 2. Click a message. You see the stars, the text and the contact details the customer left. 3. Under “Reply by email” there is a draft you can adjust. “Send reply” sends it to the customer. If they left no email address, the page tells you, and you get in touch another way. 4. Change the status and add a note if you like. Notes are only visible to your team. #### Good to know - Private feedback is never public. It doesn't appear on any review site. - Staff see feedback for the locations they look after. - With several locations, Feedback shows the chosen location. New feedback at another location is announced at the top, with “Switch there”. - You choose the form's fields under Review page, Form: first name, last name, email and phone, each Required, Optional or Off. The message is always part of it. ### Set up notifications Source: https://help.citypicked.com/en/notifications You don't want to check the app every day. Let Citypicked email you as soon as a customer is unhappy, and send a report once a week. 1. Open Settings, Notifications. Under “Recipients”, add the addresses that get the emails. Up to 10 are possible, for example yours and your store manager's. 2. “Private feedback right away” sends an email as soon as a customer writes to you privately. 3. “Low rating” tells you when someone rates you low and writes no feedback. Low means a rating your star filter doesn't send on to review sites, usually 1 to 3 stars. We wait up to 15 minutes in case feedback follows; if it does, you get the feedback email instead. 4. The “Weekly report” arrives every Monday at 8:00 AM with the week's requests, stars and new feedback. Good to know: Under “More options” is the “Reply-to address”: replies from your customers to email requests go there. Replies to SMS we email to the recipients above. Watch out: Without recipients we send no notifications. With several locations, you set them for each location. ## Send requests: SMS, email, WhatsApp, QR ### SMS, email, WhatsApp or QR code: which fits? Source: https://help.citypicked.com/en/channels You have some customers' mobile numbers, others' email addresses, and some you only see at the counter. Here is what each channel does and what it costs. #### SMS - Goes straight to the phone. Good for customers whose mobile number you have. - Uses credits: One SMS costs 1 credit, more to some countries. One SMS holds up to 160 characters, or up to 70 with emojis or characters like á, ç or curly quotes (“ ”). Letters like é, ñ and ü count normally. A longer message counts as several SMS. - Your customers see your sender name, for example your business name. - SMS to the US and Canada need a sender number first. We set it up for you; it takes about a week. #### Email - Included in your plan. Your customers tap the stars right in the email. - Basic includes 300 email requests a month, Pro 1,000 and Business 2,500. During the trial at most 100 emails go out. - Email reminders come with every plan. #### WhatsApp - Free. Citypicked prepares the message and you send it from your own WhatsApp. - You send each message one by one; for lists, use SMS or email. There are no automatic reminders here. #### QR code - Free. For customers right in front of you: at the counter, on tables, on the receipt. - You need no number and no address. Anyone who scans lands right on your review page. Tip: You don't have to pick just one. Many businesses send an SMS or email after the appointment and also put the QR code at the checkout. ### Send requests by WhatsApp Source: https://help.citypicked.com/en/whatsapp Your customers already message you on WhatsApp? Then ask them there too. Citypicked writes the message with your link, and you send it with one tap from your own WhatsApp. Free. 1. Open Requests and choose WhatsApp at the top. 2. Enter your customer's name and mobile number, check the permission box and click “Prepare WhatsApp messages”. 3. Click “Open in WhatsApp” for each customer. WhatsApp opens with the finished message to that number: on your phone in the app, on a computer in WhatsApp Web or the desktop app. 4. Tap send in WhatsApp. Done. #### Good to know - In the list the request first shows “Ready for WhatsApp”, after opening “Opened in WhatsApp”. You can open a waiting message anytime under “Actions”. - Citypicked can't see whether a WhatsApp message arrived. It counts as delivered once your customer opens the link. - You change the WhatsApp text under “Edit message”. - Since you send it yourself, WhatsApp has no automatic reminders. ### Edit your message and sender name Source: https://help.citypicked.com/en/message-text The templates already fit, but you want them in your own voice? Here is how to change the text, subject and sender name, with a preview, without breaking anything. 1. Open Requests and click “Edit message”. At the top, choose SMS, email or WhatsApp. 2. Change the text. With the Name, Company and Link placeholders, Citypicked fills in your customer's name, your business and your link when it sends. 3. On the right you see right away how the message looks on a phone. For SMS, the counter shows the characters and how many SMS it becomes. 4. Everything saves right away. “Restore default” brings the template back. #### Rules that protect you - The link to your review page must be in the message exactly once. If you delete it, Citypicked puts it back at the end. - Citypicked adds the unsubscribe line to SMS automatically. - SMS sender: 3 to 11 letters or digits, no spaces or accents. You can also change it under Settings, Sending. - Email sender: what your customers see in their inbox, at most 40 characters. The subject has at most 150 characters. Watch out: Emojis and characters like á or ç make an SMS shorter: 70 instead of 160 characters. The counter tells you which characters they are. A longer message counts as several SMS and uses more credits. Good to know: Only owners and managers can change texts. Requests that are already scheduled go out with the new text; the page tells you how many there are. ### Put up your QR code Source: https://help.citypicked.com/en/qr-code Many customers are right in front of you, but you don't have their number. A QR code on the counter, a table or the receipt takes them to your review page with one scan. 1. Go to Requests and open the QR code tab at the top. Your first code is already there. 2. Download it as PNG or SVG, or open the “Print sheet”. There you pick table card, poster, sticker or business card and click “Download PDF” or “Print”. 3. Put it where customers have a moment: at the checkout, on tables, in the waiting room or on the receipt. 4. You can create one code per place: under “New QR code”, enter a label, for example “Reception” or “Table 1”, and click “Create QR code”. #### Good to know - Each code shows how often it was scanned in the last 30 days. Under “QR scans by code” in Analytics you see which works best. - An archived code still leads to your page but is no longer counted. So you don't have to throw away anything you printed. - At the top is the link to your review page, to copy for your website, email signature or social media. - Only owners and managers can create or change QR codes. Tip: Say one sentence at checkout, for example: “If you enjoyed it, we'd love a review.” That way your customers notice the code. ### Sending times and delay Source: https://help.citypicked.com/en/sending Nobody likes a message at midnight. Here is how to set when requests go out, whether on Sundays too, and how long before a customer can get a second one. 1. Open Settings, Sending. Under “Sending hours”, pick “From” and “To”, by default 9:00 AM to 7:00 PM. Your location's local time counts: its time zone is under Settings, Locations. 2. “Also send on Sundays” is off until you switch it on. While it's off, Sunday requests go out on Monday morning. 3. Under “More options”, “Don't ask twice” sets how long a customer gets no second request after one: 7, 14, 30, 60 or 90 days, 30 by default. #### How it works out A request you send outside the sending hours waits until they begin again. If you send it at 5:05 AM, it goes out at 9:00 AM the same morning. If you send it at 8:00 PM, it goes out at 9:00 AM the next morning. The sending hours apply to reminders too. Overnight hours aren't possible: no requests go out at night. Whether a request goes out right away or later you choose when sending, under “When”. Automatic requests after an appointment wait as long as you set at the automation link under “Wait after the appointment”. Tip: You can still send a single request right away: in the list under “Actions” with “Send now”. It goes out at once, even outside your sending hours. Good to know: With several locations you set the sending hours and Sundays for each location. You choose the location at the top of the page. ### What a request's status means Source: https://help.citypicked.com/en/request-status Did my SMS arrive? Did the customer leave a review? The list under Requests shows how far each request got, and you can cancel or resend it right there. #### The statuses in order - Scheduled: waiting for the time you picked or for your sending hours. - Sent: it went out. Delivered: it arrived on the phone or in the inbox. - Not delivered: it didn't arrive, for example because of a wrong number. For an SMS you get the credits back. - Opened: your customer opened the link. - 1 to 5 stars: your customer rated. “Went to Google” (or another site): they went on to the review site. - Private feedback: your customer wrote to you privately. It shows under Feedback. - Canceled: you or your team canceled the request. - For WhatsApp: “Ready for WhatsApp” and “Opened in WhatsApp”. #### See a request's history 1. Click the customer's name in the list. The “Request history” shows every step with its time: created, sent, delivered, link opened, stars, reminders and SMS replies. 2. “Actions” on each row lists what is possible right now: “Send now”, “Send again”, “Cancel request”, “Stop all messages to this contact” and “Delete contact (privacy)”. Tip: At the top you filter by channel, status and period, or search by name, number or email. “Export as CSV” gives you the list as a file. ### When customers reply to the SMS Source: https://help.citypicked.com/en/sms-replies Some customers reply to your SMS, with a thank-you, a question or a STOP. Here is how Citypicked handles it and how you hear about it. - STOP (also UNSUBSCRIBE): the customer is unsubscribed and gets no more messages from you on any channel. Scheduled requests and reminders are canceled. They get a short confirmation. - START: the customer is subscribed again. - HELP or INFO: the customer gets a short answer with your business name and how to unsubscribe. - Any other reply we email to you, to the addresses under Settings, Notifications. Good to know: Citypicked does not send replies by SMS. If you want to answer, it's best to get in touch with your customer directly, for example by phone. The automatic answers to STOP, START and HELP use no credits. Tip: The request history shows every SMS reply, including STOP and START. ## Review page and star filter ### How the star filter works Source: https://help.citypicked.com/en/star-filter Happy customers should go to Google, and criticism should come to you first. The star filter uses the stars to decide what your customer sees next. 1. Customers first give stars on your review page. 2. From the limit you set, for example 4 stars and up, they see the buttons to Google and your other review sites. If you have only one review site, they go straight there. 3. Below it they get a private form. The message only comes to you, under Feedback, and by email right away if you like. 4. Under Review page, Star filter you switch the filter on or off and choose under “Good ratings start at”: 3, 4 or 5 stars. #### Good to know - With the star filter off, every customer sees the review sites, whatever they rate. - “Also offer private feedback on good ratings” gives happy customers a way to write to you privately as well. - You change the texts for good and bad ratings under Review page, Texts. In the preview you switch between Stars, Good, Bad and Thanks. - Stars via your QR code or the direct link count just like stars from a request. ### Design your review page: logo, color, texts Source: https://help.citypicked.com/en/review-page-design Your customers should see right away that the page comes from you. With your logo, color and your own words it looks like part of your business. 1. Click “Review page” on the left. You see the preview on the left and the sections on the right: Look, Texts, Star filter, Review sites, Form, Link and Sharing. 2. Under “Look”, pick a color or enter your own under “Custom color”. Upload your logo and, if you like, a team or shop photo: PNG, JPG or WebP, up to 15 MB. The photo shows next to your page on computers. 3. Under “Texts” you change the headline, the line under it, the texts for good and bad ratings and the thank-you text. Citypicked replaces Company with your business name. 4. Above the preview you switch between Stars, Good, Bad and Thanks. That way you see every step your customers see. Everything saves right away. #### Which language your customers see Under Texts, “Language of your texts” is the language you write in. With “Show in the visitor's language” on, visitors whose browser is set to another language see the page in their language, with our default texts. Good to know: At the very bottom of the page there is a small “Powered by Citypicked” line. You can hide it under Sharing on Pro and Business. Only owners and managers can change the review page. ### Add and sort your review sites Source: https://help.citypicked.com/en/review-sites Google matters most for most businesses, but it isn't the only site. Here is how to add more, for example Facebook, Tripadvisor, Yelp or Trustpilot, and set their order. 1. Open Review page, Review sites and click “Add review site”. 2. Choose the site. The ones that fit your location are at the top, more below. The search finds any of them, for example “Yelp”. If yours isn't listed, use “Custom link”. 3. Paste the link to your profile on that site and click “Add”. 4. With “Up” and “Down” you set the order in which your customers see the sites. “Hide” hides a site without deleting it. #### Good to know - With one review site, Citypicked sends happy customers straight there. From two sites on, they choose. - Citypicked checks your review site links once a week. If a site can no longer find your page, it says “Please check” with the reason. - Every location has its own review sites. Tip: Less is often more: two or three sites are usually enough, with the most important one at the top. ### Share your link: website, Instagram, email signature Source: https://help.citypicked.com/en/page-link Your review page has its own link. You can share it wherever your customers are, and change it so it fits your business. 1. Under Review page, your link is right by the preview. “Copy link” puts it on your clipboard; “Open page” shows you the page itself. 2. Under “Link” you change the last part, for example to your business name: 3 to 40 characters, lowercase letters, numbers and dashes. 3. Under “Sharing” you set how the link looks when you share it by WhatsApp or email: the title and an image. Without your own image we show a card with your logo, your color and the stars. #### Good places for the link - On your website, for example as its own menu item. - In your email signature and in emailed invoices. - In your Instagram or Facebook profile. Good to know: When you change the link, your last 3 old links keep redirecting for 90 days. QR codes and links in requests always lead to the new link, so you don't need to reprint anything. ## Reviews on your website ### Show your reviews on your website Source: https://help.citypicked.com/en/website-widget With the widget, your selected reviews show right on your website. You copy a short code and paste it once, and Citypicked does the rest. 1. Pick your reviews: Open “Showcase” and bring over good reviews under “Reviews”, for example from Google. The widget shows only what you select there. 2. Set up the widget: Under “Website widget”, choose the layout, the color scheme and the number of reviews. The preview shows right away how it looks. 3. Copy the code: Under “Code for your website”, click “Copy code”. Take the code with a script if your website allows it. 4. Paste it into your website: Paste the code in your website's editor where the widget should appear, and save. WordPress, Wix, Squarespace, Shopify and Webflow each have their own guide below. #### Script or iframe? The code with a script is the first choice. It loads the widget only when visitors scroll to it, and it sets no cookies. Use the iframe code if your website does not allow scripts. It shows the same widget in a fixed frame as tall as the widget. Tip: If you change the layout or the number of reviews, copy the code again and replace the old one. Then the space on your website fits exactly. #### Common questions **Do I need to paste the code again when I add reviews?** No. The widget picks up new reviews and a new order by itself, and hidden ones disappear. **Does the widget slow down my website?** Hardly. It loads only when visitors scroll to it, and it keeps its space free so nothing jumps. ### Add the widget to WordPress Source: https://help.citypicked.com/en/widget-wordpress How to put your selected reviews on your WordPress website: with the “Custom HTML” block, in a few minutes. 1. Copy the code: In Citypicked, open “Showcase”, then “Website widget”. Click “Copy code” next to the code with a script. 2. Add the block: In WordPress, open the page in the editor. Click the plus at the top, search for “html” and pick the “Custom HTML” block. Or type /html in an empty paragraph and press Enter. 3. Paste the code: Click “Edit HTML” in the block. Paste the code into the field and click “Update”. 4. Save: Click “Save” at the top, or “Publish” for a new page. 5. Check it: Open the page on your website. The widget appears as soon as you scroll to it. #### If WordPress removes the code Only users who may add unfiltered HTML in WordPress, such as administrators, can save scripts and iframes. For other roles, WordPress removes the code when you save. Then ask someone with administrator rights to paste it. On WordPress.com, scripts and iframes need a paid plan with hosting features turned on. A paid plan alone is not enough. Tip: If you change the layout or the number of reviews, copy the code again and replace the old one. Then the space on your website fits exactly. ### Add the widget to Wix Source: https://help.citypicked.com/en/widget-wix How to put your selected reviews on your Wix website: with the “Embed HTML” element and the iframe code. 1. Copy the code: In Citypicked, open “Showcase”, then “Website widget”. Click “Copy code” next to the iframe code. 2. Add the element: In the Wix Editor, click “Add Elements” on the left. Pick “Embed Code”, then “Popular Embeds” and “Embed HTML”. 3. Paste the code: Click the new element, then “Enter Code”. Paste the code into the field and click “Apply”. 4. Adjust the size: Drag the element to full width and to the widget's height. You find the height in Citypicked below the code. 5. Publish: Click “Publish” at the top right. 6. Check it: Open the page on your website. The widget appears as soon as you scroll to it. #### Why the iframe code? Wix always shows custom code in a frame of its own. The iframe code fills that frame at full width. Wix only shows code with secure addresses (HTTPS). The Citypicked code always uses HTTPS. Tip: If you change the layout or the number of reviews, copy the code again and replace the old one. Then the space on your website fits exactly. ### Add the widget to Squarespace Source: https://help.citypicked.com/en/widget-squarespace How to put your selected reviews on your Squarespace website: with a Code block, on the Core, Plus or Advanced plan. 1. Copy the code: In Citypicked, open “Showcase”, then “Website widget”. Click “Copy code” next to the code with a script. 2. Add the block: In Squarespace, edit the page. Hover over the section, click “Add Block” or the plus, and pick “Code”. 3. Paste the code: Click the pencil icon on the block and paste the code into the text field. Then click next to the block. 4. Save: Click “Save”. To close the editor, click “Exit” and then “Save”. 5. Check it: Open the page on your website. The widget appears as soon as you scroll to it. Good to know: Squarespace allows scripts and iframes in Code blocks only on the Core, Plus or Advanced plan. On other plans you cannot add the widget there, not even with the iframe code. Tip: If you change the layout or the number of reviews, copy the code again and replace the old one. Then the space on your website fits exactly. ### Add the widget to Shopify Source: https://help.citypicked.com/en/widget-shopify How to put your selected reviews in your Shopify store: with the “Custom Liquid” section in the theme editor. 1. Copy the code: In Citypicked, open “Showcase”, then “Website widget”. Click “Copy code” next to the code with a script. 2. Open the theme editor: In Shopify, go to “Online Store” and click “Edit theme”. At the top, pick the page where the widget should appear. 3. Add the section: Click “Add section” in the sidebar and pick “Custom Liquid”. 4. Paste the code: Paste the code into the “Liquid code” field. Drag the section to the right place in the sidebar. 5. Save: Click “Save”. 6. Check it: Open the page on your website. The widget appears as soon as you scroll to it. #### If your theme has no such section Shopify's own themes such as Dawn and Horizon have the “Custom Liquid” section. If your theme lacks it, ask your theme's developer for a section for custom code. Tip: If you change the layout or the number of reviews, copy the code again and replace the old one. Then the space on your website fits exactly. ### Add the widget to Webflow Source: https://help.citypicked.com/en/widget-webflow How to put your selected reviews on your Webflow website: with the “Code Embed” element, on a paid plan. 1. Copy the code: In Citypicked, open “Showcase”, then “Website widget”. Click “Copy code” next to the code with a script. 2. Add the element: Open the page in the Webflow Designer. Click the plus on the left and drag the “Code Embed” element to where the widget should appear. 3. Paste the code: Paste the code into the window and click “Save & Close”. 4. Publish: Click “Publish” at the top right and publish the site. 5. Check it: Open the page on your website. The widget appears as soon as you scroll to it. #### Good to know on Webflow The “Code Embed” element needs a paid site plan or a Core, Growth, Agency or Freelancer Workspace. In the Designer you only see a placeholder. The widget appears on the published site. Tip: If you change the layout or the number of reviews, copy the code again and replace the old one. Then the space on your website fits exactly. ## Automate it ### Automatic after every appointment Source: https://help.citypicked.com/en/automation After a long day nobody thinks of review requests anymore. With the automation link your booking or checkout system sends every request by itself, without you typing anything. 1. Create the link: Open Settings, Sending. At “Automatic after every appointment”, click “Set up” and then “Create link”. Copy the link right away: for your security Citypicked shows it only once. 2. Set the defaults: Choose the “Default channel” (SMS or email) and the “Wait after the appointment”: from “Right away” through 15 minutes to “1 day”. Both apply when your system sends nothing else. 3. Connect your system: Many checkout and booking systems, as well as Zapier, Make and n8n, can send name and mobile number (or email) to this link after an appointment. There is a guide with an example for each tool here. Or you give the link to the company behind your software and they set it up once. Ask them for a “webhook”. 4. Check what arrives: Under “Recently received” you see the last 50 calls with time, result, channel and contact. “Accepted” means the request goes out. #### Good to know - Automation is part of Pro and Business. - The link belongs to one location. With several locations, each one gets its own. - Automatic requests keep to your sending hours and to “Don't ask twice”, like all others. - Your system can also send the channel: the value sms or email in the field channel. Watch out: Anyone who knows the link can trigger requests. Treat it like a password. If it got into the wrong hands, click “Create new link”: the old one stops working, and you put the new one into your system. ### Upload a customer list (CSV) Source: https://help.citypicked.com/en/csv-list Have a list of your recent customers in Excel or your checkout software? Upload it, and Citypicked asks them all at once or spread out, without you typing each one. 1. File: Go to Requests and click “Upload a list (CSV)”. Choose an Excel or CSV file, one row per customer, with name and mobile number or email. Up to 10,000 rows and 5 MB. “Download template” gives you a file that fits. 2. Columns: Citypicked usually finds name, phone and email by itself. Quickly check that each column is right. Set columns you don't need to “Ignore”. 3. Check: Citypicked shows how many rows are valid and which are not valid, already asked, twice in the file or unsubscribed, with the reason. Under “Send by”, choose SMS or email. 4. Send: Under “When to send?”, pick “All at once” or “Spread out”: a set number of requests per hour within your sending hours. Check the permission box and click “Send now”, or “Schedule” when the list is spread out or waits for your sending hours. Tip: “Spread out” looks more natural and spreads the replies over the day too. “Download rows with problems” gives you the rows that don't fit as a file to fix. #### Good to know - If the file comes from Numbers or Google Sheets, save it as an Excel file. - Nobody is asked twice: anyone who got a request recently is left out, even if you upload the list twice. - The list shows under Sent as “List with … contacts”. There you see how many have gone out, and “Cancel the rest” stops the rest. - You send WhatsApp messages one by one. For lists, choose SMS or email. ### Connect Zapier to Citypicked Source: https://help.citypicked.com/en/automation-zapier Zapier connects thousands of apps, for example your calendar, your checkout system or your shop. After an appointment or a sale, Zapier sends your customer's name and mobile number (or email) to your automation link, and Citypicked sends the request. 1. In Citypicked, open Settings, Sending, “Automatic after every appointment” and click “Set up”. Create your automation link there and copy it right away: Citypicked shows it only once. 2. Create a new Zap in Zapier. As the trigger, pick the app your customer data comes from and the matching event, for example a finished appointment or a paid invoice. 3. Add the action “Webhooks by Zapier” with the event “POST”. Paste your automation link into “URL” and set “Payload Type” to “json”. 4. Under “Data”, add one row per field: on the left the field name as in the example below (name, phone or email, delay_minutes if you like), on the right the matching value from your trigger. 5. Click “Test step”. The test sends a real request, so best use your own number. Then switch the Zap on. 6. Under “Recently received” Citypicked shows everything your system sent, with its result, so you see right away whether everything arrives. Good to know: Zapier counts “Webhooks by Zapier” among its premium apps. Check in Zapier whether your plan includes it. ``` { "name": "Anna Miller", "phone": "+1 201 555 0123", "delay_minutes": 60 } ``` ### Connect Make to Citypicked Source: https://help.citypicked.com/en/automation-make In Make you build workflows from modules. After an appointment, an invoice or a job, the “HTTP” module sends your customer's name and mobile number (or email) to your automation link, and Citypicked sends the request. 1. In Citypicked, open Settings, Sending, “Automatic after every appointment” and click “Set up”. Create your automation link there and copy it right away: Citypicked shows it only once. 2. Create a new scenario in Make. The first module is the app your customer data comes from, for example your calendar or your checkout system. 3. Then add the “HTTP” module with “Make a request”. Paste your automation link into “URL” and set the method to “POST”. 4. Choose JSON (application/json) as the content and paste the example below. Replace the sample values with the fields from your first module: click into the text and pick the field from the list. 5. Run the scenario once with “Run once”. The test sends a real request, so best use your own number. Then switch the scenario on. 6. Under “Recently received” Citypicked shows everything your system sent, with its result, so you see right away whether everything arrives. ``` { "name": "Anna Miller", "phone": "+1 201 555 0123", "delay_minutes": 60 } ``` ### Connect n8n to Citypicked Source: https://help.citypicked.com/en/automation-n8n n8n is a workflow tool you can also run yourself. The “HTTP Request” node sends your customer's name and mobile number (or email) to your automation link, and Citypicked sends the request. 1. In Citypicked, open Settings, Sending, “Automatic after every appointment” and click “Set up”. Create your automation link there and copy it right away: Citypicked shows it only once. 2. Start your workflow with the node your customer data comes from, for example your calendar or your shop. 3. Add the “HTTP Request” node. Set “Method” to “POST” and paste your automation link into “URL”. 4. Switch on “Send Body”, set “Body Content Type” to “JSON” and “Specify Body” to “Using JSON”, and paste the example below. Replace the sample values with the fields from the previous node: simply drag them into the text. 5. Run the node once. The test sends a real request, so best use your own number. Then activate the workflow. 6. Under “Recently received” Citypicked shows everything your system sent, with its result, so you see right away whether everything arrives. ``` { "name": "Anna Miller", "phone": "+1 201 555 0123", "delay_minutes": 60 } ``` ### Connect Shopify Flow to Citypicked Source: https://help.citypicked.com/en/automation-shopify-flow With Shopify Flow every customer gets a review request by itself after their order ships. The “Send HTTP request” action sends name, mobile number and email from the order to your automation link. Shopify offers this action on the Grow, Advanced and Plus plans. On Basic, use Zapier. 1. In Citypicked, open Settings, Sending, “Automatic after every appointment” and click “Set up”. Create your automation link there and copy it right away: Citypicked shows it only once. 2. Open the Flow app in Shopify and create a new workflow. Choose “Order fulfilled” as the trigger, so a shipped order. 3. Add the “Send HTTP request” action. Set the method to “POST”, paste your automation link into “URL” and add the header “Content-Type” with the value “application/json”. 4. Paste the example below as the body. Shopify fills in the values in the double curly braces for every order. 5. With delay_minutes you choose how long Citypicked waits: 4320 minutes are 3 days, by then the parcel has usually arrived. 7 days is the most. 6. If the order has both a mobile number and an email, Citypicked uses your link's default channel. If one is missing, Citypicked uses the other. 7. Switch the workflow on. Under “Recently received” Citypicked shows everything your system sent, with its result, so you see right away whether everything arrives. ``` { "name": "{{order.customer.firstName}}", "phone": "{{order.phone}}", "email": "{{order.email}}", "delay_minutes": 4320 } ``` ### Connect Calendly to Citypicked Source: https://help.citypicked.com/en/automation-calendly Anyone who books with you through Calendly gets a review request by itself after the appointment. The easiest way to connect both is Zapier: Zapier waits until the appointment ends and then sends name and email (or mobile number) to your automation link. 1. In Citypicked, open Settings, Sending, “Automatic after every appointment” and click “Set up”. Create your automation link there and copy it right away: Citypicked shows it only once. 2. Create a new Zap in Zapier. Trigger: Calendly with the event “Invitee Created”, so a newly booked appointment. 3. Add “Delay by Zapier” with “Delay Until” and pick the end time of the appointment. That way the request only goes out after the appointment. 4. Add the action “Webhooks by Zapier” with the event “POST”. Paste your automation link into “URL” and set “Payload Type” to “json”. 5. Under “Data”, add these rows: name with the invitee's name and email with their email. If you ask for the mobile number in Calendly, map it to phone and the request goes by text message. 6. Test the Zap with an appointment you book with yourself, then switch it on. Under “Recently received” Citypicked shows everything your system sent, with its result, so you see right away whether everything arrives. Good to know: Zapier counts “Webhooks by Zapier” among its premium apps. Check in Zapier whether your plan includes it. ``` { "name": "Anna Miller", "email": "anna@example.com", "delay_minutes": 30 } ``` ### Connect Google Calendar to Citypicked Source: https://help.citypicked.com/en/automation-google-calendar If you keep your customer appointments in Google Calendar, a review request goes out by itself after every appointment. The connection runs through Zapier: when an appointment ends, Zapier sends the contact details from the appointment to your automation link. 1. In Citypicked, open Settings, Sending, “Automatic after every appointment” and click “Set up”. Create your automation link there and copy it right away: Citypicked shows it only once. 2. Put the contact details into your appointments so Zapier finds them: the customer's email address as a guest, or only their mobile number in the description. You can write the name in the title. 3. Create a new Zap in Zapier. Trigger: Google Calendar with the event “Event Ended”, so a finished appointment. Pick the calendar with your customer appointments. 4. Add the action “Webhooks by Zapier” with the event “POST”. Paste your automation link into “URL” and set “Payload Type” to “json”. 5. Under “Data”, add these rows: name with the appointment's title and email with the guest's email. If the mobile number is in the description, map the description to phone instead. 6. Test the Zap with a short appointment for yourself, then switch it on. Under “Recently received” Citypicked shows everything your system sent, with its result, so you see right away whether everything arrives. Good to know: Zapier counts “Webhooks by Zapier” among its premium apps. Check in Zapier whether your plan includes it. ``` { "name": "Anna Miller", "email": "anna@example.com", "delay_minutes": 60 } ``` ### Connect Google Sheets to Citypicked Source: https://help.citypicked.com/en/automation-google-sheets If you keep your customers in a Google Sheet, a review request goes out by itself for every new row. The connection runs through Zapier: Zapier sends the name and mobile number (or email) from the new row to your automation link. 1. In Citypicked, open Settings, Sending, “Automatic after every appointment” and click “Set up”. Create your automation link there and copy it right away: Citypicked shows it only once. 2. Give your sheet columns for name and mobile number (or email), with the headings in the first row. 3. Create a new Zap in Zapier. Trigger: Google Sheets with the event “New Spreadsheet Row”, so a new row. Pick your spreadsheet and the sheet. 4. Add the action “Webhooks by Zapier” with the event “POST”. Paste your automation link into “URL” and set “Payload Type” to “json”. 5. Under “Data”, add these rows: name with the column for the name and phone with the column for the mobile number (or email with the column for the email). 6. Add a test row with your own number and switch the Zap on. Under “Recently received” Citypicked shows everything your system sent, with its result, so you see right away whether everything arrives. Tip: A finished list with many customers at once is easier to upload as a CSV file under Requests. Good to know: Zapier counts “Webhooks by Zapier” among its premium apps. Check in Zapier whether your plan includes it. ``` { "name": "Anna Miller", "phone": "+1 201 555 0123", "delay_minutes": 60 } ``` ## Analytics and weekly report ### Read your analytics: from request to review Source: https://help.citypicked.com/en/analytics Is asking worth it? Analytics shows you in numbers how many customers open your requests, how many stars they give and how many go on to Google. 1. Click “Analytics” on the left and choose the period at the top: 7 days, 30 days, 90 days or 12 months. 2. At the top you see five numbers: Requests, Opened, Avg. stars, Went to review sites and Private feedback. 3. “From request to review” shows how far the customers you asked get: Sent, Delivered, Opened, Rated, Went to review sites. Where most drop off is worth a look. 4. Further down: the trend over time, the star distribution, requests by channel, channels compared, QR scans by code and who on your team sends how many requests. “Export as CSV” gives you the numbers as a file. #### How Citypicked counts - Requests and Opened count your requests. Avg. stars, Went to review sites and Private feedback count every customer, also via QR code and direct link. - A star tapped in the email counts as opened too. - WhatsApp counts as delivered only once the customer opens the link, since Citypicked can't see whether the message arrived there. - The test request from setup is never counted, nor its stars and feedback. Tip: Few opened? Try another time or another channel. Many opened but few rated? Look at your review page on your phone, the way your customers see it. ### The weekly report by email Source: https://help.citypicked.com/en/weekly-report Once a week, the essentials without opening the app: the weekly report lands in your inbox every Monday morning. #### What it contains - Requests sent and how many of them were opened. - Ratings, average stars and how many customers went to review sites. - New private feedback, with a nudge to reply soon. - A “View analytics” button for all the numbers. #### Switch it on or off, change who gets it 1. Open Settings, Notifications and switch the “Weekly report” on or off. 2. The report goes to every address under “Recipients”. With several locations, each location gets its own report. Good to know: The report arrives on Mondays from 8:00 AM in your location's time zone and covers the week before, Monday to Sunday. Your test request doesn't count. ## Team and locations ### Invite your team and choose roles Source: https://help.citypicked.com/en/team Everyone at the front desk should be able to send requests, but not everyone should touch your plan or review page. Invite your team and give each person the right role. 1. Go to Settings, Team and click “Invite team member”. 2. Enter the email address, the name if you like, and choose the role. With several locations, you also choose which ones they see. 3. Click “Send invitation”. Your team member gets an email with a link, clicks “Accept invitation” and is in. Everyone logs in with their own email address. #### The roles - Owner: can do everything, including plan and team. - Manager: can do everything except plan and team. - Staff: can send requests and read feedback for their locations. They can't change the review page, the QR codes or the business settings. Good to know: Basic includes 3 team seats (including yours), Pro and Business any number. You and open invitations count. Settings, Team shows how many team seats are taken. Tip: You can change a role anytime right in the list. “Remove” means the person can no longer log in; the requests they sent are kept. You can resend or withdraw an open invitation. ### Run several locations Source: https://help.citypicked.com/en/locations Two branches, a practice at two addresses? Each location gets its own review page, its own messages and its own numbers, all in one account. 1. Open Settings, Locations and click “Add location”. How many locations your plan includes is shown at the top. 2. Enter the name and address. Under “Time zone, phone, website” you set the time zone; the sending hours follow it. 3. With “Copy settings from” you copy messages, sending times, notifications and the review page texts from another location. You add review sites afterwards. 4. Click “Create location”. At the top of the app you switch between locations. Overview, Requests, Review page, Feedback and Analytics then show only the chosen one. #### Good to know - Basic and Pro include 1 location, Business 3. You add more locations under Settings, Plan & credits. - An archived location takes no more reviews, its page says it isn't active right now, and no more requests go out. All data is kept, and you can restore it anytime. - The last active location can't be archived. - New feedback at another location still counts in the number next to Feedback. ## Billing, plans and SMS credits ### Your free trial: how it works Source: https://help.citypicked.com/en/trial You want to try Citypicked before you pay. Here is how the trial works, what you can already do in it and what happens afterwards. - To start, you choose a plan and add your card. Nothing is charged today. - If you cancel before the trial ends, you pay nothing. We then also turn off automatic top-up. - When your trial ends is shown under Settings, Plan & credits. Shortly before, a notice in the app reminds you. - After that your plan continues, and the first invoice is charged to your card. #### What's different during the trial - At most 100 email requests go out. After the trial your full monthly volume applies. - Your plan's SMS only come with the first paid invoice. For texts during the trial, buy a pack under Plan & credits. - Changing plans costs nothing during the trial. Your trial continues; you pay only after it ends. ### Change your plan: Basic, Pro and Business Source: https://help.citypicked.com/en/plans More customers, a second location, or you want automation? Here is how to change your plan, and how to see beforehand what changes. #### How the plans differ - Basic includes 300 email requests a month, Pro 1,000 and Business 2,500. - Basic includes no SMS, Pro 50 SMS a month and Business 400. - Basic and Pro include 1 location, Business 3. - Basic includes 3 team seats (including yours), Pro and Business any number. - Automation and SMS reminders come with Pro and Business. #### How to switch 1. Open Settings, Plan & credits and under “More options” the “Change plan” section. That's also where you choose between Monthly and Yearly. 2. Click “Switch to …”. Citypicked shows you beforehand what changes, for example when a smaller plan doesn't include automation. 3. Confirm with “Switch now”. For the rest of this period you only pay the difference, on the next invoice. Good to know: More emails a month, a set number of SMS or more locations you set under “Emails, SMS and locations”. Only the account owner changes the plan and billing. ### SMS credits Source: https://help.citypicked.com/en/credits Email and WhatsApp are included in your plan; SMS need credits. Here is how it works, what a text costs and where the credits come from. - One SMS costs 1 credit, more to some countries. One SMS holds up to 160 characters, or up to 70 with emojis or characters like á, ç or curly quotes (“ ”). Letters like é, ñ and ü count normally. A longer message counts as several SMS. Citypicked shows the cost before sending. - Basic includes no SMS, Pro 50 SMS a month and Business 400. Each paid invoice adds your plan's SMS to your credits, so none come during the free days. With yearly billing, the yearly invoice brings all 12 months at once: 600 credits on Pro. - Need more SMS, or want to send texts during the free days? Buy a pack under Settings, Plan & credits. - So you never run out, Citypicked can buy a pack by itself with “Top up automatically”. - Credits don't expire while you have an active plan. - If an SMS isn't delivered, you get the credits back. The history lists it as “Refunded”. - SMS to the US and Canada need a sender number first. We set it up for you; it takes about a week. Tip: You always see your balance at the bottom left of the app. Below it you see how much of it is already reserved for scheduled SMS. Good to know: “History” lists every credit and charge, newest first. The account owner buys credits. ### Top up credits automatically Source: https://help.citypicked.com/en/auto-top-up Credits run out in the middle of the day and texts stay unsent? With “Top up automatically”, Citypicked buys a pack before that happens. 1. Open Settings, Plan & credits and under “More options” the “Top up automatically” section. 2. Choose a pack and under “Top up at” the limit: below 20, 50 or 100 credits. Then click “Save”. #### How it works - When your credits drop below the limit, Citypicked buys the pack and charges it to the card on file. This happens at most once in 24 hours and at most 3 times a month. - If your credits aren't enough for an SMS request, Citypicked tops up before sending, and then the request goes out. The form tells you beforehand. - Every top-up shows in the history, and you get a confirmation by email. The invoice is under “Payment method and invoices”. Watch out: If a payment doesn't go through, nothing is charged, automatic top-up turns off, and we email you. Check your payment method, then switch it on again. Good to know: Automatic top-up works once a card is on file, so from the start of your trial. Only the owner can set it. ### Invoices and payment method Source: https://help.citypicked.com/en/invoices Your bookkeeper needs the invoice, or your card expired? You handle both with one click under Plan & credits. 1. Open Settings, Plan & credits and click “Payment method and invoices”. 2. This opens Stripe's secure page; Stripe handles our payments. There you change your card and download your invoices. On a slow connection it can take a while to load. #### If a payment doesn't go through The app then shows a notice with the date by which you should update your payment method. “Update payment method” takes you right there. After that, everything continues right away. If the payment stays open, sending pauses. Your review page and QR codes keep working, and your data stays. Good to know: For every credit pack you buy you get a confirmation by email; the invoice is also under “Payment method and invoices”. ### Cancel your subscription Source: https://help.citypicked.com/en/cancel Canceling takes one click, no phone call and no form. Here is what happens to your page, your credits and your data afterwards. 1. Go to Settings, Plan & credits and click “Cancel plan”. 2. Confirm with “Cancel plan”. Your plan runs until the end of the paid period; nothing more is charged after that. You get a confirmation by email. 3. Until the last day, one click on “Keep my plan” undoes the cancellation. #### What happens afterwards - Cancel during the trial and you pay nothing. We then also turn off automatic top-up. - Credits don't expire while you have an active plan. - After your plan ends, your review page stays online for 30 more days. - Your data stays. If you choose a plan again later, you carry on where you left off. Good to know: If you also want all data deleted, delete the account under Settings, Account. If you only want to stop paying, canceling is enough. ## Account, privacy and data ### Your account: profile, data export and deleting Source: https://help.citypicked.com/en/account Your name, language, a copy of all your data, or deleting the account altogether: everything about your own account is under Settings, Account. - Under Settings, Account you change your name and the language. You log in with your email address. - “Log out everywhere” logs you out on every device, including this one. For example when you lost a phone. - “Export data” creates a ZIP file with all contacts, requests, messages, feedback and settings as CSV files. We email you the link. Only the account owner can download it. - “Delete account” ends the plan at once and stops all requests. After 14 days all data is deleted and can't be restored. To be safe, you type your account's name. Only the owner can delete the account. Tip: If you only want to stop paying, canceling the plan is enough. Your data stays. ### Log in without a password Source: https://help.citypicked.com/en/login No password to remember: you log in with a link we email you. 1. Click “Log in” on the website and enter your email address. We email you a login link. 2. Open the email from Citypicked and click the link. It works for 30 minutes. 3. If the login page shows “Continue with Google”, you can use that too, as long as your account has the same address as your Google account. #### If it doesn't work - No email? Check your spam folder and the spelling of the address. After a short wait you can send the link again. - “This link has expired”: no problem, “Send a new link” gets you a new one. - “Too many attempts. Please wait a few minutes.” After that it works again. - Team members log in with their own address. If an invitation expired, ask the person who invited you to send it again. ### Permission and unsubscribing Source: https://help.citypicked.com/en/consent You don't want to bother anyone, and you want to be on the safe side. Here is how Citypicked asks for permission and how a customer unsubscribes. - Only send requests to customers who agreed, for example because they gave you their number. - Citypicked asks for permission on every send and records who confirmed it. - Every SMS and email has an unsubscribe link. Anyone who unsubscribes gets no more messages from you through Citypicked. With SMS, replying STOP works too. - If a customer asks you to stop, click “Actions” on one of their requests and then “Stop all messages to this contact”. They then get nothing more, by SMS, email or WhatsApp, and scheduled requests are canceled. - “Don't ask twice” makes sure a customer gets no second request for a while after one. ### Delete a customer's data (privacy) Source: https://help.citypicked.com/en/delete-contact A customer wants you to delete their data? It takes two clicks, and your Analytics numbers stay right. 1. Open Requests and find the customer with the search field under “Sent”, by name, number or email. 2. Click “Actions” on one of their requests and then “Delete contact (privacy)”. 3. Confirm. The customer is deleted with number, address, messages and private feedback. The numbers in Analytics stay, without the name. Watch out: This cannot be undone. If the customer only wants no more messages, use “Stop all messages to this contact” instead. ## Troubleshooting ### The SMS doesn't arrive Source: https://help.citypicked.com/en/sms-not-arriving You sent a request, but your customer says nothing arrived? Usually the reason is already in the list. Go through these points in order. 1. Look at the status: Open Requests and find the customer under “Sent”. Click their name: the history shows every step with its time. 2. “Scheduled”: it's still waiting: The request is waiting for the time you picked or for your sending hours. Nothing goes out on Sundays while “Also send on Sundays” is off. “Send now” under “Actions” sends it at once. 3. “Not delivered”: The number couldn't receive the SMS, for example because it is wrong or not a mobile number. Check the number with your customer and send again. You get the credits back. 4. “Delivered”, but nothing there: Then the SMS reached the phone. Some phones sort messages from unknown senders into a separate folder. Ask your customer to look there. #### Reasons Citypicked shows when sending - “Number or address not valid” or “Landline, SMS not possible”: the number doesn't work. Use a mobile number, best with the country code. - “Not enough credits”: buy credits or switch on “Top up automatically”. - “Already asked on … (Don't ask twice)”: the customer got a request recently. - “Unsubscribed”: the customer wants no more messages, so nothing goes out to them anymore. - “No SMS sender for this country yet”: SMS to the US and Canada need a sender number first. We set it up for you; it takes about a week. Until then, ask these customers by email. Watch out: Some countries don't deliver generic sender names like “Info”. Under Settings, Sending, “SMS sender”, use your business name: 3 to 11 letters or digits, no spaces or accents. Tip: Send a request to your own number. If it reaches you, the cause is almost always the customer's number or phone. ### The email doesn't arrive Source: https://help.citypicked.com/en/email-not-arriving Your customer can't find the review request email? Here is how to find out whether it went out, and what you can do. 1. Open Requests, find the customer under “Sent” and look at the status. “Delivered” means the email reached your customer's mailbox. 2. Ask your customer to check the spam or promotions folder. The sender is your business name, and the subject asks about their visit. 3. If it says “Not delivered” or “Address cannot receive emails”, the address is usually misspelled, for example gmial.com instead of gmail.com. Fix it and send again. #### If no emails go out at all - “Emails for this month used up”: you reached your monthly volume. Raise it under Settings, Plan & credits, or ask by SMS. During the trial at most 100 emails go out. - “Email requests are paused”: many emails of your last requests didn't arrive, or several customers marked them as spam. To protect your sending, no email requests go out then. SMS, WhatsApp and QR codes keep working. Reply to our email about it and we'll look at it together. Tip: Only send to addresses of customers who really visited you and agreed. Old lists often hold addresses that no longer exist. ### The link doesn't open Source: https://help.citypicked.com/en/link-not-opening A customer says the link in the message doesn't work, or your review page isn't active? These are the most common reasons. - Links in texts start with citypicked.com/t/ and a short code. If the link was typed by hand or cut off when copied, it can't find the request. Send the request again: “Actions”, “Send again”. - “This page is not active right now.” Then the location is archived. Restore it under Settings, Locations. - If you changed your review page's link, your last 3 old links keep redirecting for 90 days. After that, an old link no longer leads to you. QR codes and links in requests aren't affected. - After your plan ends, your review page stays online for 30 more days. While a payment is open, the page and QR codes keep working. #### Test it yourself 1. Open Review page and click “Open page”. That's how you see your page like a customer. 2. Send yourself a request and open the link on your phone. If that works, the problem is that one link, not your page. ### The Google link goes to the wrong place Source: https://help.citypicked.com/en/wrong-review-link Your customers end up at another business, in Google search or on an error page instead of the review window? Then your Google link is off. Here is how to fix it. 1. Open Google Maps and find your business. Make sure it really is your listing: name, address and photos. 2. Tap “Share” and then “Copy link”. 3. In Citypicked, open Review page, Review sites. Click “Edit” at Google, paste the new link and save. 4. Test it: open your page, give 5 stars and tap Google. #### Which links work - The share link from Google Maps (maps.app.goo.gl/…), a full Google Maps link to your business, or the link from “Ask for reviews” in your Business Profile (g.page/r/…). - When Citypicked can identify your business from it, customers land right in Google's review window. Otherwise your listing on Google opens, and your customers tap “Write a review” there. - “This link leads nowhere. Please copy it again in Google Maps.” Then the link expired or is incomplete. - “Please check” next to a review site: during the weekly check, the site couldn't find your page, or the address doesn't exist. Copy the link to your profile again. Tip: The free tool “Create your Google review link and QR code” on our website shows you whether your Maps link opens the review window directly. ### Automatic requests don't go out Source: https://help.citypicked.com/en/automation-not-working You connected your booking or checkout system, but no requests go out? “Recently received” almost always says why. 1. Open Settings, Sending, “Automatic after every appointment” and look under “Recently received”. It lists the last 50 calls with their result. 2. If nothing is listed, your system never called the link. Check that the full link is entered and sent as “POST”. If you no longer have the link, create a new one and enter it. 3. If it says “Accepted”, all is well. The request then waits for the wait after the appointment and your sending hours. It shows under Requests as “Scheduled”. #### The most common results - “Already asked”: the customer got a request recently (Don't ask twice). - “Invalid data” with a reason, for example “Neither a mobile number nor an email address” or “Data not readable (not valid JSON or form data)”: your system doesn't send the fields the way Citypicked needs them. Compare them with the example in the guide for your tool. - “No credits”: there are no credits for SMS. Buy credits, switch on “Top up automatically” or set the default channel to email. - “Too many requests from your system”: it sent more calls per minute or per day than your plan allows. - “Refused: automation isn't part of your plan”: the link is paused. On a plan with automation the same link works again, without setting anything up again. ## Developer docs in full ### Citypicked for developers Send review requests, read ratings and private feedback, and get your numbers. From your own software, an automation tool or an AI assistant. Source: https://citypicked.com/en/developers #### What you can do - Send review requests by SMS or email, right away or planned up to 30 days ahead - Get a location's review link, also as a QR code - Read requests with their status and stars, plus private feedback from the review page - Pull the numbers for any range of days - Get events at your own URL the moment a customer rates or leaves feedback Everything runs on the same rules as the app. Same checks, same credits, same guard against asking a customer twice. #### Three ways in - [MCP](https://citypicked.com/en/developers/mcp): Connect Claude, ChatGPT, Claude Code or Cursor. Your assistant sends and reads in plain language. - [REST API](https://citypicked.com/en/developers/api): JSON over HTTPS with an API key. Every endpoint with examples in curl, JavaScript and Python. - [CLI](https://citypicked.com/en/developers/cli): Requests, links, feedback and numbers from the terminal and from scripts. #### Addresses | What | Address | | --- | --- | | REST API | `https://api.citypicked.com/v1` | | MCP server | `https://mcp.citypicked.com/mcp` | | OpenAPI document | [https://api.citypicked.com/v1/openapi.json](https://api.citypicked.com/v1/openapi.json) | #### Where to start 1. Follow the [quickstart](https://citypicked.com/en/developers/quickstart). Make a key, make a first call, send one request, read the result. 2. Read how [keys and scopes](https://citypicked.com/en/developers/authentication) work. 3. Look up any endpoint in the [API reference](https://citypicked.com/en/developers/api). #### Machine-readable - The [OpenAPI 3.1 document](https://api.citypicked.com/v1/openapi.json) describes every endpoint and every event - Every page of these docs is also plain Markdown, with .md at the end of its address - [llms.txt](https://citypicked.com/llms.txt) and [llms-full.txt](https://citypicked.com/llms-full.txt) list the pages for AI assistants ### Quickstart Four steps from nothing to your first review request. You need a Citypicked account and a terminal. Source: https://citypicked.com/en/developers/quickstart #### 1. Make an API key 1. In the app, open **Settings > Sending > API** ([go there](https://app.citypicked.com/settings/api)). Owners and managers can do this. 2. Name the key and pick **Read and send requests** if it should send. 3. Copy the key right away. It is shown only once. Keep it in a variable for the next steps. The examples show a placeholder. ```bash export CITYPICKED_API_KEY=cp_live_xxxxxxxx ``` > Sending requests through the API is part of the Pro and Business plans. Reading works on every plan. #### 2. Make your first call `GET /v1/me` shows the account, the key and whether it can send. Every working key gets 200 here. curl: ```bash curl "https://api.citypicked.com/v1/me" \ -H "Authorization: Bearer cp_live_xxxxxxxx" ``` JavaScript: ```js const res = await fetch("https://api.citypicked.com/v1/me", { headers: { Authorization: "Bearer cp_live_xxxxxxxx", }, }); const data = await res.json(); console.log(res.status, data); ``` Python: ```python import requests res = requests.get( "https://api.citypicked.com/v1/me", headers={"Authorization": "Bearer cp_live_xxxxxxxx"}, ) print(res.status_code, res.json()) ``` Answer ```json { "account": { "id": "0b7c8f4e-5a1d-4c2e-9f3a-6d8e2b1c4a70", "name": "Sunnyside Salon", "plan": "pro", "language": "en", "time_zone": "America/New_York" }, "key": { "name": "Booking system", "scopes": [ "read", "send" ] }, "can_send": true, "credits": { "balance": 180 } } ``` #### 3. Send one review request `POST /v1/requests` sends to up to 100 customers at once. `consent: true` confirms the customers agreed to hear from you. With an `Idempotency-Key`, the same call within 24 hours goes out only once. curl: ```bash curl -X POST "https://api.citypicked.com/v1/requests" \ -H "Authorization: Bearer cp_live_xxxxxxxx" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: booking-1042" \ -d '{ "location_id": "5b1d0c7e-2f4a-4c3e-9a51-7d2e8f1b6a40", "channel": "email", "consent": true, "contacts": [ { "name": "Emma Clark", "email": "emma@example.com" } ] }' ``` JavaScript: ```js const res = await fetch("https://api.citypicked.com/v1/requests", { method: "POST", headers: { Authorization: "Bearer cp_live_xxxxxxxx", "Content-Type": "application/json", "Idempotency-Key": "booking-1042", }, body: JSON.stringify({ "location_id": "5b1d0c7e-2f4a-4c3e-9a51-7d2e8f1b6a40", "channel": "email", "consent": true, "contacts": [ { "name": "Emma Clark", "email": "emma@example.com" } ] }), }); const data = await res.json(); console.log(res.status, data); ``` Python: ```python import requests res = requests.post( "https://api.citypicked.com/v1/requests", headers={"Authorization": "Bearer cp_live_xxxxxxxx", "Idempotency-Key": "booking-1042"}, json={ "location_id": "5b1d0c7e-2f4a-4c3e-9a51-7d2e8f1b6a40", "channel": "email", "consent": True, "contacts": [ { "name": "Emma Clark", "email": "emma@example.com", }, ], }, ) print(res.status_code, res.json()) ``` Answer ```json { "results": [ { "index": 0, "outcome": "created", "request_id": "c3f1a9e2-7b4d-4e8a-b6c2-1f9d3e5a7b20" } ], "created": 1 } ``` - `created` means the request is on its way - `duplicate` means this customer was asked recently. The window is 30 days unless the account changed it in the app - `opted_out`, `invalid`, `no_credits` and `consent_method_missing` say why nothing went out > For SMS, use `"channel": "sms"` and a number like `+1 201 555 0123`. SMS to US and Canadian numbers also need `consent_method` and go out from the business's own number once the carriers have checked its business details. #### 4. Read the result Use the `request_id` from the answer to check where it stands. `status` moves from `sent` through `delivered` and `clicked` to `rated`, and `rating` shows the stars. curl: ```bash curl "https://api.citypicked.com/v1/requests/c3f1a9e2-7b4d-4e8a-b6c2-1f9d3e5a7b20" \ -H "Authorization: Bearer cp_live_xxxxxxxx" ``` JavaScript: ```js const res = await fetch("https://api.citypicked.com/v1/requests/c3f1a9e2-7b4d-4e8a-b6c2-1f9d3e5a7b20", { headers: { Authorization: "Bearer cp_live_xxxxxxxx", }, }); const data = await res.json(); console.log(res.status, data); ``` Python: ```python import requests res = requests.get( "https://api.citypicked.com/v1/requests/c3f1a9e2-7b4d-4e8a-b6c2-1f9d3e5a7b20", headers={"Authorization": "Bearer cp_live_xxxxxxxx"}, ) print(res.status_code, res.json()) ``` Answer ```json { "id": "c3f1a9e2-7b4d-4e8a-b6c2-1f9d3e5a7b20", "location_id": "5b1d0c7e-2f4a-4c3e-9a51-7d2e8f1b6a40", "channel": "email", "source": "api", "status": "rated", "contact": { "name": "Emma C.", "email": "em***@example.com", "phone": null }, "rating": 5, "created_at": "2026-11-08T09:30:00Z", "sent_at": "2026-11-08T09:30:04Z", "clicked_at": "2026-11-08T10:12:40Z", "rated_at": "2026-11-08T10:13:05Z" } ``` Instead of asking again, you can have it sent to you. With [webhooks](https://citypicked.com/en/developers/webhooks) every rating and every piece of feedback arrives at your URL right away. ### Authentication API keys for your own code and scripts, sign-in with Citypicked for AI assistants. Both act as a person on your team, never more. Source: https://citypicked.com/en/developers/authentication #### API keys Owners and managers make keys in the app under **Settings > Sending > API** ([go there](https://app.citypicked.com/settings/api)). Send the key on every call. ``` Authorization: Bearer cp_live_xxxxxxxx ``` - Every key starts with `cp_live_` and is shown once, when it is made - An account can have up to 20 active keys - A key acts as the person who made it, with their role and locations at the moment of the call - Anything outside that reach answers 404, exactly like an id that does not exist - A key stops working when its person leaves the team, changes role or locations, or logs out everywhere - Revoke a key in the same place in the app. Everything that uses it stops at once #### Scopes | Scope | In the app | What it allows | | --- | --- | --- | | `read` | Read | Every `GET`, so the account, locations, review links, requests, feedback, numbers and webhooks | | `send` | Read and send requests | Also `POST /v1/requests`, canceling a planned request, and adding or removing webhooks | Every key has `read`. Sending through the API is part of the Pro and Business plans; reading works on every plan. `GET /v1/me` answers `can_send` so you can check before you send. #### OAuth for MCP AI assistants connect to the MCP server at `https://mcp.citypicked.com/mcp` without a key. The person signs in to Citypicked, sees what the assistant asks for and picks **Allow**. Details for each assistant are on the [MCP page](https://citypicked.com/en/developers/mcp). - Only owners and managers can allow a connection, and it acts as the person who allowed it - The consent screen shows the assistant, the account, the locations and the two scopes - A connection allowed to read only gets 403 `insufficient_scope` when it tries to send, so the assistant can ask again - Every connected assistant is listed under **Settings > Sending > API** and can be disconnected there at any time ##### For client developers Standard OAuth 2.1 with PKCE (S256). The MCP server publishes its protected resource metadata at `/.well-known/oauth-protected-resource/mcp` and names `https://app.citypicked.com` as the authorization server, whose metadata is at `https://app.citypicked.com/.well-known/oauth-authorization-server`. - Client ID metadata documents are supported, and dynamic client registration for clients without one - Scopes are `read` and `send`; the resource is the MCP address above - Access tokens last an hour, refresh tokens rotate on every use #### Keep keys safe - Keep keys on your server, never in a browser, an app bundle or a public repository - Give each tool its own key, so you can revoke one without touching the rest - Give a key `send` only when it needs to send ### API reference Every endpoint of the REST API with its parameters, an example call, an example answer and its errors. Generated from the OpenAPI document. Source: https://citypicked.com/en/developers/api Base URL `https://api.citypicked.com/v1`. JSON in and out, times in ISO 8601 UTC, ids are UUIDs. Send your key as `Authorization: Bearer cp_live_xxxxxxxx` ([how](https://citypicked.com/en/developers/authentication)). Lists take `limit` (1 to 100, default 25) and `cursor`, and answer `next_cursor`, which is null on the last page. The [OpenAPI document](https://api.citypicked.com/v1/openapi.json) has the full schemas. Errors and limits are on [Rate limits and errors](https://citypicked.com/en/developers/errors). #### Account ##### GET /v1/me The account, this key and whether it can send. Scope: `read`. The connection test of every integration: 200 for every working key. curl: ```bash curl "https://api.citypicked.com/v1/me" \ -H "Authorization: Bearer cp_live_xxxxxxxx" ``` JavaScript: ```js const res = await fetch("https://api.citypicked.com/v1/me", { headers: { Authorization: "Bearer cp_live_xxxxxxxx", }, }); const data = await res.json(); console.log(res.status, data); ``` Python: ```python import requests res = requests.get( "https://api.citypicked.com/v1/me", headers={"Authorization": "Bearer cp_live_xxxxxxxx"}, ) print(res.status_code, res.json()) ``` Answer 200: ```json { "account": { "id": "0b7c8f4e-5a1d-4c2e-9f3a-6d8e2b1c4a70", "name": "Sunnyside Salon", "plan": "pro", "language": "en", "time_zone": "America/New_York" }, "key": { "name": "Booking system", "scopes": [ "read", "send" ] }, "can_send": true, "credits": { "balance": 180 } } ``` **Answer fields** | Name | Type | Description | | --- | --- | --- | | `account` | object | | | `account.id` | uuid | | | `account.name` | string | | | `account.plan` | string or null | | | `account.language` | string | | | `account.time_zone` | string | | | `key` | object | | | `key.name` | string | | | `key.scopes` | array of (read | send) | | | `can_send` | boolean | False when the key lacks send, the plan has no API sending, or the account is behind the plan wall. | | `credits` | object | | | `credits.balance` | integer | SMS credits (one SMS uses one or more by length and country; e-mail uses none). | **Errors** - 400: invalid_request: the query or body is not valid (field names the wrong one), or the webhook URL is not a public https address - 401: unauthorized: no API key, or the key does not work (revoked, or its member can no longer use it) - 403: forbidden (scope missing, plan without API sending) or account_closed - 429: rate_limited: wait for Retry-After seconds - 500: server_error: something went wrong on our side #### Locations ##### GET /v1/locations The locations this key can see. Scope: `read`. Newest first by created_at. Pass next_cursor as cursor for the next page; null means there is none. Polling triggers dedupe by id and filter with since. **Parameters** | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `limit` | query | string | no | 1 to 100, default 25. | | `cursor` | query | string | no | next_cursor of the previous answer. | curl: ```bash curl "https://api.citypicked.com/v1/locations" \ -H "Authorization: Bearer cp_live_xxxxxxxx" ``` JavaScript: ```js const res = await fetch("https://api.citypicked.com/v1/locations", { headers: { Authorization: "Bearer cp_live_xxxxxxxx", }, }); const data = await res.json(); console.log(res.status, data); ``` Python: ```python import requests res = requests.get( "https://api.citypicked.com/v1/locations", headers={"Authorization": "Bearer cp_live_xxxxxxxx"}, ) print(res.status_code, res.json()) ``` Answer 200: ```json { "data": [ { "id": "5b1d0c7e-2f4a-4c3e-9a51-7d2e8f1b6a40", "name": "Sunnyside Salon Downtown", "country": "US", "review_link": "https://go.citypicked.com/sunnyside-salon", "created_at": "2026-11-08T09:30:00Z" } ], "next_cursor": null } ``` **Answer fields** | Name | Type | Description | | --- | --- | --- | | `data` | array of object | | | `data[].id` | uuid | | | `data[].name` | string | | | `data[].country` | string | | | `data[].review_link` | string or null | | | `data[].created_at` | string (date-time) | | | `next_cursor` | string or null | Pass as cursor for the next page; null when there is none. | **Errors** - 400: invalid_request: the query or body is not valid (field names the wrong one), or the webhook URL is not a public https address - 401: unauthorized: no API key, or the key does not work (revoked, or its member can no longer use it) - 403: forbidden (scope missing, plan without API sending) or account_closed - 429: rate_limited: wait for Retry-After seconds - 500: server_error: something went wrong on our side ##### GET /v1/locations/{id}/review-link A location's review page link, or its QR code as SVG or PNG. Scope: `read`. **Parameters** | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | uuid | yes | | | `format` | query | json | svg | png | no | json (default): the link. svg or png (1024 px): its QR code as an image. | curl: ```bash curl "https://api.citypicked.com/v1/locations/5b1d0c7e-2f4a-4c3e-9a51-7d2e8f1b6a40/review-link" \ -H "Authorization: Bearer cp_live_xxxxxxxx" ``` JavaScript: ```js const res = await fetch("https://api.citypicked.com/v1/locations/5b1d0c7e-2f4a-4c3e-9a51-7d2e8f1b6a40/review-link", { headers: { Authorization: "Bearer cp_live_xxxxxxxx", }, }); const data = await res.json(); console.log(res.status, data); ``` Python: ```python import requests res = requests.get( "https://api.citypicked.com/v1/locations/5b1d0c7e-2f4a-4c3e-9a51-7d2e8f1b6a40/review-link", headers={"Authorization": "Bearer cp_live_xxxxxxxx"}, ) print(res.status_code, res.json()) ``` Answer 200: ```json { "location_id": "5b1d0c7e-2f4a-4c3e-9a51-7d2e8f1b6a40", "url": "https://go.citypicked.com/sunnyside-salon", "qr_svg_url": "https://api.citypicked.com/v1/locations/5b1d0c7e-2f4a-4c3e-9a51-7d2e8f1b6a40/review-link?format=svg", "qr_png_url": "https://api.citypicked.com/v1/locations/5b1d0c7e-2f4a-4c3e-9a51-7d2e8f1b6a40/review-link?format=png" } ``` With `format=svg` or `format=png` the answer is the QR code itself (`image/svg+xml`, or `image/png` at 1024 px) instead of JSON. **Answer fields** | Name | Type | Description | | --- | --- | --- | | `location_id` | uuid | | | `url` | string | | | `qr_svg_url` | string | | | `qr_png_url` | string | | **Errors** - 400: invalid_request: the query or body is not valid (field names the wrong one), or the webhook URL is not a public https address - 401: unauthorized: no API key, or the key does not work (revoked, or its member can no longer use it) - 403: forbidden (scope missing, plan without API sending) or account_closed - 404: not_found: nothing with this id for this key - 429: rate_limited: wait for Retry-After seconds - 500: server_error: something went wrong on our side #### Requests ##### GET /v1/requests Review requests. Scope: `read`. Newest first by created_at. Pass next_cursor as cursor for the next page; null means there is none. Polling triggers dedupe by id and filter with since. Triggers: status=rated (request rated), status=public_click (a click to a review site). **Parameters** | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `location_id` | query | uuid | no | Only this location's requests. | | `status` | query | scheduled | sent | delivered | failed | clicked | rated | public_click | feedback | canceled | no | Has reached: rated lists every request with a rating (also those that went on to public_click or feedback), public_click every one whose customer went on to a review site, clicked every opened one; scheduled and canceled are the current status. | | `since` | query | string (date-time) | no | Inclusive. Without status: created at or after. With status: reached that status at or after (rated_at, ...). | | `limit` | query | string | no | 1 to 100, default 25. | | `cursor` | query | string | no | next_cursor of the previous answer. | curl: ```bash curl "https://api.citypicked.com/v1/requests?status=rated&limit=10" \ -H "Authorization: Bearer cp_live_xxxxxxxx" ``` JavaScript: ```js const res = await fetch("https://api.citypicked.com/v1/requests?status=rated&limit=10", { headers: { Authorization: "Bearer cp_live_xxxxxxxx", }, }); const data = await res.json(); console.log(res.status, data); ``` Python: ```python import requests res = requests.get( "https://api.citypicked.com/v1/requests", headers={"Authorization": "Bearer cp_live_xxxxxxxx"}, params={ "status": "rated", "limit": "10", }, ) print(res.status_code, res.json()) ``` Answer 200: ```json { "data": [ { "id": "c3f1a9e2-7b4d-4e8a-b6c2-1f9d3e5a7b20", "location_id": "5b1d0c7e-2f4a-4c3e-9a51-7d2e8f1b6a40", "channel": "email", "source": "api", "status": "rated", "contact": { "name": "Emma C.", "email": "em***@example.com", "phone": null }, "rating": 5, "created_at": "2026-11-08T09:30:00Z", "sent_at": "2026-11-08T09:30:04Z", "clicked_at": "2026-11-08T10:12:40Z", "rated_at": "2026-11-08T10:13:05Z" } ], "next_cursor": null } ``` **Answer fields** | Name | Type | Description | | --- | --- | --- | | `data` | array of object | | | `data[].id` | uuid | | | `data[].location_id` | uuid | | | `data[].channel` | sms | email | whatsapp | qr | | | `data[].source` | manual | csv | trigger | qr | integration | api | | | `data[].status` | scheduled | sent | delivered | failed | clicked | rated | public_click | feedback | canceled | | | `data[].contact` | object | Masked the way the app's sent list masks contacts: Anna M., an***@example.org, +49 170 ... 67. | | `data[].contact.name` | string or null | | | `data[].contact.email` | string or null | | | `data[].contact.phone` | string or null | | | `data[].rating` | integer or null | | | `data[].created_at` | string (date-time) | | | `data[].sent_at` | string (date-time) or null | | | `data[].clicked_at` | string (date-time) or null | | | `data[].rated_at` | string (date-time) or null | | | `next_cursor` | string or null | Pass as cursor for the next page; null when there is none. | **Errors** - 400: invalid_request: the query or body is not valid (field names the wrong one), or the webhook URL is not a public https address - 401: unauthorized: no API key, or the key does not work (revoked, or its member can no longer use it) - 403: forbidden (scope missing, plan without API sending) or account_closed - 404: not_found: nothing with this id for this key - 429: rate_limited: wait for Retry-After seconds - 500: server_error: something went wrong on our side ##### POST /v1/requests Send review requests. Scope: `send`. The same way as the send form: same checks, same credits, same duplicate window. Up to 100 contacts per call, body up to 256 KB. Each contact gets an outcome; a call where no contact can be sent still answers 201, only a malformed body is 400. Idempotency-Key, per API key: the same key within 24 hours returns the first answer and sends nothing again; with a different body it is 409 idempotency_conflict. send_at plans the requests up to 30 days ahead inside the sending hours. A WhatsApp result carries whatsapp_url: the business sends it from its own phone. Needs the send scope and a plan with API sending; behind the plan wall 402. **Parameters** | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Idempotency-Key` | header | string | no | Any string up to 200 characters, per API key. The same key within 24 hours returns the first answer and sends nothing again; with another body 409 idempotency_conflict. | **Body** | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `location_id` | body | uuid | no | Optional when the key sees exactly one location. | | `channel` | body | sms | email | whatsapp | yes | | | `send_at` | body | string (date-time) | no | When to send, at most 30 days ahead; default now. Moved into the location's sending hours like a send from the app. | | `consent` | body | true | yes | You confirm that these customers agreed to be contacted. | | `consent_method` | body | booking | in_person | form | written | no | Required for SMS to US and CA numbers: how these customers agreed. | | `contacts` | body | array of object | yes | | | `contacts[].name` | body | string | no | The customer's name, used in the message. | | `contacts[].email` | body | string | no | Needed for channel email. | | `contacts[].phone` | body | string | no | Needed for sms and whatsapp. International format (+49 170 ...) or the location's national format. | curl: ```bash curl -X POST "https://api.citypicked.com/v1/requests" \ -H "Authorization: Bearer cp_live_xxxxxxxx" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: booking-1042" \ -d '{ "location_id": "5b1d0c7e-2f4a-4c3e-9a51-7d2e8f1b6a40", "channel": "email", "consent": true, "contacts": [ { "name": "Emma Clark", "email": "emma@example.com" } ] }' ``` JavaScript: ```js const res = await fetch("https://api.citypicked.com/v1/requests", { method: "POST", headers: { Authorization: "Bearer cp_live_xxxxxxxx", "Content-Type": "application/json", "Idempotency-Key": "booking-1042", }, body: JSON.stringify({ "location_id": "5b1d0c7e-2f4a-4c3e-9a51-7d2e8f1b6a40", "channel": "email", "consent": true, "contacts": [ { "name": "Emma Clark", "email": "emma@example.com" } ] }), }); const data = await res.json(); console.log(res.status, data); ``` Python: ```python import requests res = requests.post( "https://api.citypicked.com/v1/requests", headers={"Authorization": "Bearer cp_live_xxxxxxxx", "Idempotency-Key": "booking-1042"}, json={ "location_id": "5b1d0c7e-2f4a-4c3e-9a51-7d2e8f1b6a40", "channel": "email", "consent": True, "contacts": [ { "name": "Emma Clark", "email": "emma@example.com", }, ], }, ) print(res.status_code, res.json()) ``` Answer 201: ```json { "results": [ { "index": 0, "outcome": "created", "request_id": "c3f1a9e2-7b4d-4e8a-b6c2-1f9d3e5a7b20" } ], "created": 1 } ``` **Answer fields** | Name | Type | Description | | --- | --- | --- | | `results` | array of object | | | `results[].index` | integer | Position in contacts. | | `results[].outcome` | created | duplicate | invalid | opted_out | no_credits | consent_method_missing | | | `results[].request_id` | uuid or null | | | `results[].message` | string | Why, when the outcome is not created. | | `results[].whatsapp_url` | string | WhatsApp only, created: the wa.me link the business opens on its own phone to send the message, as in the app. | | `created` | integer | | **Errors** - 400: invalid_request: the query or body is not valid (field names the wrong one), or the webhook URL is not a public https address - 401: unauthorized: no API key, or the key does not work (revoked, or its member can no longer use it) - 402: plan_wall: the account cannot send (trial over or plan without access) - 403: forbidden (scope missing, plan without API sending) or account_closed - 404: not_found: nothing with this id for this key - 409: idempotency_conflict (the Idempotency-Key came with another body) or not_cancelable (the request already went out) - 429: rate_limited: wait for Retry-After seconds - 500: server_error: something went wrong on our side ##### POST /v1/requests/{id}/cancel Cancel a request that is still planned. Scope: `send`. 200 with the request (status canceled, also when it was canceled before); 409 not_cancelable once it went out. **Parameters** | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | uuid | yes | | curl: ```bash curl -X POST "https://api.citypicked.com/v1/requests/c3f1a9e2-7b4d-4e8a-b6c2-1f9d3e5a7b20/cancel" \ -H "Authorization: Bearer cp_live_xxxxxxxx" ``` JavaScript: ```js const res = await fetch("https://api.citypicked.com/v1/requests/c3f1a9e2-7b4d-4e8a-b6c2-1f9d3e5a7b20/cancel", { method: "POST", headers: { Authorization: "Bearer cp_live_xxxxxxxx", }, }); const data = await res.json(); console.log(res.status, data); ``` Python: ```python import requests res = requests.post( "https://api.citypicked.com/v1/requests/c3f1a9e2-7b4d-4e8a-b6c2-1f9d3e5a7b20/cancel", headers={"Authorization": "Bearer cp_live_xxxxxxxx"}, ) print(res.status_code, res.json()) ``` Answer 200: ```json { "id": "c3f1a9e2-7b4d-4e8a-b6c2-1f9d3e5a7b20", "location_id": "5b1d0c7e-2f4a-4c3e-9a51-7d2e8f1b6a40", "channel": "email", "source": "api", "status": "canceled", "contact": { "name": "Emma C.", "email": "em***@example.com", "phone": null }, "rating": null, "created_at": "2026-11-08T09:30:00Z", "sent_at": null, "clicked_at": null, "rated_at": null } ``` **Answer fields** | Name | Type | Description | | --- | --- | --- | | `id` | uuid | | | `location_id` | uuid | | | `channel` | sms | email | whatsapp | qr | | | `source` | manual | csv | trigger | qr | integration | api | | | `status` | scheduled | sent | delivered | failed | clicked | rated | public_click | feedback | canceled | | | `contact` | object | Masked the way the app's sent list masks contacts: Anna M., an***@example.org, +49 170 ... 67. | | `contact.name` | string or null | | | `contact.email` | string or null | | | `contact.phone` | string or null | | | `rating` | integer or null | | | `created_at` | string (date-time) | | | `sent_at` | string (date-time) or null | | | `clicked_at` | string (date-time) or null | | | `rated_at` | string (date-time) or null | | **Errors** - 400: invalid_request: the query or body is not valid (field names the wrong one), or the webhook URL is not a public https address - 401: unauthorized: no API key, or the key does not work (revoked, or its member can no longer use it) - 403: forbidden (scope missing, plan without API sending) or account_closed - 404: not_found: nothing with this id for this key - 409: idempotency_conflict (the Idempotency-Key came with another body) or not_cancelable (the request already went out) - 429: rate_limited: wait for Retry-After seconds - 500: server_error: something went wrong on our side ##### GET /v1/requests/{id} One review request. Scope: `read`. **Parameters** | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | uuid | yes | | curl: ```bash curl "https://api.citypicked.com/v1/requests/c3f1a9e2-7b4d-4e8a-b6c2-1f9d3e5a7b20" \ -H "Authorization: Bearer cp_live_xxxxxxxx" ``` JavaScript: ```js const res = await fetch("https://api.citypicked.com/v1/requests/c3f1a9e2-7b4d-4e8a-b6c2-1f9d3e5a7b20", { headers: { Authorization: "Bearer cp_live_xxxxxxxx", }, }); const data = await res.json(); console.log(res.status, data); ``` Python: ```python import requests res = requests.get( "https://api.citypicked.com/v1/requests/c3f1a9e2-7b4d-4e8a-b6c2-1f9d3e5a7b20", headers={"Authorization": "Bearer cp_live_xxxxxxxx"}, ) print(res.status_code, res.json()) ``` Answer 200: ```json { "id": "c3f1a9e2-7b4d-4e8a-b6c2-1f9d3e5a7b20", "location_id": "5b1d0c7e-2f4a-4c3e-9a51-7d2e8f1b6a40", "channel": "email", "source": "api", "status": "rated", "contact": { "name": "Emma C.", "email": "em***@example.com", "phone": null }, "rating": 5, "created_at": "2026-11-08T09:30:00Z", "sent_at": "2026-11-08T09:30:04Z", "clicked_at": "2026-11-08T10:12:40Z", "rated_at": "2026-11-08T10:13:05Z" } ``` **Answer fields** | Name | Type | Description | | --- | --- | --- | | `id` | uuid | | | `location_id` | uuid | | | `channel` | sms | email | whatsapp | qr | | | `source` | manual | csv | trigger | qr | integration | api | | | `status` | scheduled | sent | delivered | failed | clicked | rated | public_click | feedback | canceled | | | `contact` | object | Masked the way the app's sent list masks contacts: Anna M., an***@example.org, +49 170 ... 67. | | `contact.name` | string or null | | | `contact.email` | string or null | | | `contact.phone` | string or null | | | `rating` | integer or null | | | `created_at` | string (date-time) | | | `sent_at` | string (date-time) or null | | | `clicked_at` | string (date-time) or null | | | `rated_at` | string (date-time) or null | | **Errors** - 400: invalid_request: the query or body is not valid (field names the wrong one), or the webhook URL is not a public https address - 401: unauthorized: no API key, or the key does not work (revoked, or its member can no longer use it) - 403: forbidden (scope missing, plan without API sending) or account_closed - 404: not_found: nothing with this id for this key - 429: rate_limited: wait for Retry-After seconds - 500: server_error: something went wrong on our side #### Feedback ##### GET /v1/feedback Private feedback from the review page. Scope: `read`. Newest first by created_at. Pass next_cursor as cursor for the next page; null means there is none. Polling triggers dedupe by id and filter with since. Trigger: new private feedback. **Parameters** | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `location_id` | query | uuid | no | Only this location's feedback. | | `status` | query | new | in_progress | resolved | no | | | `since` | query | string (date-time) | no | Only items created at or after this time (ISO 8601, inclusive). | | `limit` | query | string | no | 1 to 100, default 25. | | `cursor` | query | string | no | next_cursor of the previous answer. | curl: ```bash curl "https://api.citypicked.com/v1/feedback?status=new" \ -H "Authorization: Bearer cp_live_xxxxxxxx" ``` JavaScript: ```js const res = await fetch("https://api.citypicked.com/v1/feedback?status=new", { headers: { Authorization: "Bearer cp_live_xxxxxxxx", }, }); const data = await res.json(); console.log(res.status, data); ``` Python: ```python import requests res = requests.get( "https://api.citypicked.com/v1/feedback", headers={"Authorization": "Bearer cp_live_xxxxxxxx"}, params={ "status": "new", }, ) print(res.status_code, res.json()) ``` Answer 200: ```json { "data": [ { "id": "9e2a4c6b-1d3f-4a5e-8b7c-2e4f6a8c0d10", "location_id": "5b1d0c7e-2f4a-4c3e-9a51-7d2e8f1b6a40", "request_id": "c3f1a9e2-7b4d-4e8a-b6c2-1f9d3e5a7b20", "rating": 2, "text": "We waited 20 minutes past our booking time.", "contact": { "name": "Emma C." }, "status": "new", "created_at": "2026-11-08T09:30:00Z" } ], "next_cursor": null } ``` **Answer fields** | Name | Type | Description | | --- | --- | --- | | `data` | array of object | | | `data[].id` | uuid | | | `data[].location_id` | uuid | | | `data[].request_id` | uuid or null | | | `data[].rating` | integer or null | | | `data[].text` | string or null | | | `data[].contact` | object | | | `data[].contact.name` | string or null | | | `data[].status` | new | in_progress | resolved | | | `data[].created_at` | string (date-time) | | | `next_cursor` | string or null | Pass as cursor for the next page; null when there is none. | **Errors** - 400: invalid_request: the query or body is not valid (field names the wrong one), or the webhook URL is not a public https address - 401: unauthorized: no API key, or the key does not work (revoked, or its member can no longer use it) - 403: forbidden (scope missing, plan without API sending) or account_closed - 404: not_found: nothing with this id for this key - 429: rate_limited: wait for Retry-After seconds - 500: server_error: something went wrong on our side #### Stats ##### GET /v1/stats Numbers for a range of days. Scope: `read`. One location or every location the key sees, in the account's time zone, as Analytics counts them. **Parameters** | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `location_id` | query | uuid | no | Only this location; every location the key sees otherwise. | | `from` | query | string (date) | no | First day (YYYY-MM-DD, in the account's time zone, inclusive), default 30 days ago; at most 366 days before to. | | `to` | query | string (date) | no | Last day (inclusive), default today. | curl: ```bash curl "https://api.citypicked.com/v1/stats?from=2026-10-10&to=2026-11-08" \ -H "Authorization: Bearer cp_live_xxxxxxxx" ``` JavaScript: ```js const res = await fetch("https://api.citypicked.com/v1/stats?from=2026-10-10&to=2026-11-08", { headers: { Authorization: "Bearer cp_live_xxxxxxxx", }, }); const data = await res.json(); console.log(res.status, data); ``` Python: ```python import requests res = requests.get( "https://api.citypicked.com/v1/stats", headers={"Authorization": "Bearer cp_live_xxxxxxxx"}, params={ "from": "2026-10-10", "to": "2026-11-08", }, ) print(res.status_code, res.json()) ``` Answer 200: ```json { "location_id": "5b1d0c7e-2f4a-4c3e-9a51-7d2e8f1b6a40", "from": "2026-10-10", "to": "2026-11-08", "requests_sent": 120, "clicked": 64, "rated": 41, "average_rating": 4.6, "public_clicks": 33, "feedback": 8 } ``` **Answer fields** | Name | Type | Description | | --- | --- | --- | | `location_id` | uuid or null | | | `from` | string (date) | | | `to` | string (date) | | | `requests_sent` | integer | | | `clicked` | integer | Requests whose link was opened. | | `rated` | integer | Requests that got stars. | | `average_rating` | number or null | All stars of the days, as Analytics shows them (with QR code and direct link guests). | | `public_clicks` | integer | Requests that went on to a review site. | | `feedback` | integer | Private feedback received. | **Errors** - 400: invalid_request: the query or body is not valid (field names the wrong one), or the webhook URL is not a public https address - 401: unauthorized: no API key, or the key does not work (revoked, or its member can no longer use it) - 403: forbidden (scope missing, plan without API sending) or account_closed - 404: not_found: nothing with this id for this key - 429: rate_limited: wait for Retry-After seconds - 500: server_error: something went wrong on our side #### Webhooks ##### GET /v1/webhooks The webhooks this key made. Scope: `read`. Newest first by created_at. Pass next_cursor as cursor for the next page; null means there is none. Polling triggers dedupe by id and filter with since. Without secret: it is shown only when a webhook is made. **Parameters** | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `limit` | query | string | no | 1 to 100, default 25. | | `cursor` | query | string | no | next_cursor of the previous answer. | curl: ```bash curl "https://api.citypicked.com/v1/webhooks" \ -H "Authorization: Bearer cp_live_xxxxxxxx" ``` JavaScript: ```js const res = await fetch("https://api.citypicked.com/v1/webhooks", { headers: { Authorization: "Bearer cp_live_xxxxxxxx", }, }); const data = await res.json(); console.log(res.status, data); ``` Python: ```python import requests res = requests.get( "https://api.citypicked.com/v1/webhooks", headers={"Authorization": "Bearer cp_live_xxxxxxxx"}, ) print(res.status_code, res.json()) ``` Answer 200: ```json { "data": [ { "id": "7a5c3e1b-9d2f-4b6a-8c4e-3f1a5c7e9b30", "url": "https://example.com/hooks/citypicked", "events": [ "feedback.created", "request.rated" ], "status": "active", "disabled_reason": null, "created_at": "2026-11-08T09:30:00Z", "last_success_at": "2026-11-08T09:30:04Z", "last_failure_at": null } ], "next_cursor": null } ``` **Answer fields** | Name | Type | Description | | --- | --- | --- | | `data` | array of object | | | `data[].id` | uuid | | | `data[].url` | string | | | `data[].events` | array of (feedback.created | request.rated | request.public_click | request.sent | request.failed) | | | `data[].status` | active | disabled | | | `data[].disabled_reason` | failing | gone | key_revoked or null | failing: deliveries kept failing; gone: the URL answered 410; key_revoked: the key that made it stopped. | | `data[].created_at` | string (date-time) | | | `data[].last_success_at` | string (date-time) or null | | | `data[].last_failure_at` | string (date-time) or null | | | `next_cursor` | string or null | Pass as cursor for the next page; null when there is none. | **Errors** - 400: invalid_request: the query or body is not valid (field names the wrong one), or the webhook URL is not a public https address - 401: unauthorized: no API key, or the key does not work (revoked, or its member can no longer use it) - 403: forbidden (scope missing, plan without API sending) or account_closed - 429: rate_limited: wait for Retry-After seconds - 500: server_error: something went wrong on our side ##### POST /v1/webhooks Subscribe a URL to events (REST hook). Scope: `send`. The URL gets each chosen event as a signed POST (see webhooks). https with a host name that resolves to public addresses only, checked now and before every delivery. The answer carries the signing secret, once. At most 50 webhooks per account. A webhook belongs to this key: it gets the events of the locations the key can see, and it stops when the key is revoked. **Body** | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `url` | body | string | yes | https, a host name (no IP address, no user:password), at most 2000 characters, resolving to public addresses only. | | `events` | body | array of (feedback.created | request.rated | request.public_click | request.sent | request.failed) | yes | The events this URL gets. | curl: ```bash curl -X POST "https://api.citypicked.com/v1/webhooks" \ -H "Authorization: Bearer cp_live_xxxxxxxx" \ -H "Content-Type: application/json" \ -d '{ "url": "https://example.com/hooks/citypicked", "events": [ "feedback.created", "request.rated" ] }' ``` JavaScript: ```js const res = await fetch("https://api.citypicked.com/v1/webhooks", { method: "POST", headers: { Authorization: "Bearer cp_live_xxxxxxxx", "Content-Type": "application/json", }, body: JSON.stringify({ "url": "https://example.com/hooks/citypicked", "events": [ "feedback.created", "request.rated" ] }), }); const data = await res.json(); console.log(res.status, data); ``` Python: ```python import requests res = requests.post( "https://api.citypicked.com/v1/webhooks", headers={"Authorization": "Bearer cp_live_xxxxxxxx"}, json={ "url": "https://example.com/hooks/citypicked", "events": [ "feedback.created", "request.rated", ], }, ) print(res.status_code, res.json()) ``` Answer 201: ```json { "id": "7a5c3e1b-9d2f-4b6a-8c4e-3f1a5c7e9b30", "url": "https://example.com/hooks/citypicked", "events": [ "feedback.created", "request.rated" ], "status": "active", "disabled_reason": null, "created_at": "2026-11-08T09:30:00Z", "last_success_at": null, "last_failure_at": null, "secret": "cpwh_xxxxxxxx" } ``` **Answer fields** | Name | Type | Description | | --- | --- | --- | | `id` | uuid | | | `url` | string | | | `events` | array of (feedback.created | request.rated | request.public_click | request.sent | request.failed) | | | `status` | active | disabled | | | `disabled_reason` | failing | gone | key_revoked or null | failing: deliveries kept failing; gone: the URL answered 410; key_revoked: the key that made it stopped. | | `created_at` | string (date-time) | | | `last_success_at` | string (date-time) or null | | | `last_failure_at` | string (date-time) or null | | | `secret` | string | cpwh_...: signs every delivery (Citypicked-Signature). Shown only in this answer. | **Errors** - 400: invalid_request: the query or body is not valid (field names the wrong one), or the webhook URL is not a public https address - 401: unauthorized: no API key, or the key does not work (revoked, or its member can no longer use it) - 403: forbidden (scope missing, plan without API sending) or account_closed - 429: rate_limited: wait for Retry-After seconds - 500: server_error: something went wrong on our side ##### GET /v1/webhooks/{id} One webhook of this key. Scope: `read`. **Parameters** | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | uuid | yes | | curl: ```bash curl "https://api.citypicked.com/v1/webhooks/7a5c3e1b-9d2f-4b6a-8c4e-3f1a5c7e9b30" \ -H "Authorization: Bearer cp_live_xxxxxxxx" ``` JavaScript: ```js const res = await fetch("https://api.citypicked.com/v1/webhooks/7a5c3e1b-9d2f-4b6a-8c4e-3f1a5c7e9b30", { headers: { Authorization: "Bearer cp_live_xxxxxxxx", }, }); const data = await res.json(); console.log(res.status, data); ``` Python: ```python import requests res = requests.get( "https://api.citypicked.com/v1/webhooks/7a5c3e1b-9d2f-4b6a-8c4e-3f1a5c7e9b30", headers={"Authorization": "Bearer cp_live_xxxxxxxx"}, ) print(res.status_code, res.json()) ``` Answer 200: ```json { "id": "7a5c3e1b-9d2f-4b6a-8c4e-3f1a5c7e9b30", "url": "https://example.com/hooks/citypicked", "events": [ "feedback.created", "request.rated" ], "status": "active", "disabled_reason": null, "created_at": "2026-11-08T09:30:00Z", "last_success_at": "2026-11-08T09:30:04Z", "last_failure_at": null } ``` **Answer fields** | Name | Type | Description | | --- | --- | --- | | `id` | uuid | | | `url` | string | | | `events` | array of (feedback.created | request.rated | request.public_click | request.sent | request.failed) | | | `status` | active | disabled | | | `disabled_reason` | failing | gone | key_revoked or null | failing: deliveries kept failing; gone: the URL answered 410; key_revoked: the key that made it stopped. | | `created_at` | string (date-time) | | | `last_success_at` | string (date-time) or null | | | `last_failure_at` | string (date-time) or null | | **Errors** - 400: invalid_request: the query or body is not valid (field names the wrong one), or the webhook URL is not a public https address - 401: unauthorized: no API key, or the key does not work (revoked, or its member can no longer use it) - 403: forbidden (scope missing, plan without API sending) or account_closed - 404: not_found: nothing with this id for this key - 429: rate_limited: wait for Retry-After seconds - 500: server_error: something went wrong on our side ##### DELETE /v1/webhooks/{id} Unsubscribe: remove a webhook of this key. Scope: `send`. Nothing is delivered to it afterwards. **Parameters** | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | uuid | yes | | curl: ```bash curl -X DELETE "https://api.citypicked.com/v1/webhooks/7a5c3e1b-9d2f-4b6a-8c4e-3f1a5c7e9b30" \ -H "Authorization: Bearer cp_live_xxxxxxxx" ``` JavaScript: ```js const res = await fetch("https://api.citypicked.com/v1/webhooks/7a5c3e1b-9d2f-4b6a-8c4e-3f1a5c7e9b30", { method: "DELETE", headers: { Authorization: "Bearer cp_live_xxxxxxxx", }, }); const data = await res.json(); console.log(res.status, data); ``` Python: ```python import requests res = requests.delete( "https://api.citypicked.com/v1/webhooks/7a5c3e1b-9d2f-4b6a-8c4e-3f1a5c7e9b30", headers={"Authorization": "Bearer cp_live_xxxxxxxx"}, ) print(res.status_code, res.json()) ``` Answer 200: ```json { "id": "7a5c3e1b-9d2f-4b6a-8c4e-3f1a5c7e9b30", "deleted": true } ``` **Answer fields** | Name | Type | Description | | --- | --- | --- | | `id` | uuid | | | `deleted` | true | | **Errors** - 400: invalid_request: the query or body is not valid (field names the wrong one), or the webhook URL is not a public https address - 401: unauthorized: no API key, or the key does not work (revoked, or its member can no longer use it) - 403: forbidden (scope missing, plan without API sending) or account_closed - 404: not_found: nothing with this id for this key - 429: rate_limited: wait for Retry-After seconds - 500: server_error: something went wrong on our side #### OpenAPI document ##### GET /v1/openapi.json This document. No key needed. curl: ```bash curl "https://api.citypicked.com/v1/openapi.json" ``` JavaScript: ```js const res = await fetch("https://api.citypicked.com/v1/openapi.json"); const data = await res.json(); console.log(res.status, data); ``` Python: ```python import requests res = requests.get( "https://api.citypicked.com/v1/openapi.json", ) print(res.status_code, res.json()) ``` Answer 200: ```json { "openapi": "3.1.0", "info": { "title": "Citypicked API", "version": "..." }, "paths": "..." } ``` The whole document is OpenAPI 3.1, including the events under `webhooks`. Import it into Postman, Insomnia or a code generator. ### Webhooks Get each rating, click and piece of private feedback at your own URL within seconds, signed so you know it came from Citypicked. Source: https://citypicked.com/en/developers/webhooks #### Subscribe A key with the `send` scope subscribes a URL with `POST /v1/webhooks` ([reference](https://citypicked.com/en/developers/api#createWebhook)). The answer carries the signing secret, once. Keep it next to your key. curl: ```bash curl -X POST "https://api.citypicked.com/v1/webhooks" \ -H "Authorization: Bearer cp_live_xxxxxxxx" \ -H "Content-Type: application/json" \ -d '{ "url": "https://example.com/hooks/citypicked", "events": [ "feedback.created", "request.rated" ] }' ``` JavaScript: ```js const res = await fetch("https://api.citypicked.com/v1/webhooks", { method: "POST", headers: { Authorization: "Bearer cp_live_xxxxxxxx", "Content-Type": "application/json", }, body: JSON.stringify({ "url": "https://example.com/hooks/citypicked", "events": [ "feedback.created", "request.rated" ] }), }); const data = await res.json(); console.log(res.status, data); ``` Python: ```python import requests res = requests.post( "https://api.citypicked.com/v1/webhooks", headers={"Authorization": "Bearer cp_live_xxxxxxxx"}, json={ "url": "https://example.com/hooks/citypicked", "events": [ "feedback.created", "request.rated", ], }, ) print(res.status_code, res.json()) ``` - The URL must be https with a host name that resolves to public addresses only - Up to 50 webhooks per account - A webhook belongs to the key that made it, gets the events of the locations that key can see, and stops when the key is revoked - Remove one with `DELETE /v1/webhooks/{id}` #### Events | Event | When | | --- | --- | | `feedback.created` | New private feedback from the review page | | `request.rated` | A request's customer gave stars, the first time | | `request.public_click` | A request's customer went on to a review site, the first time | | `request.sent` | A request went out | | `request.failed` | A request could not be sent or was not delivered | Each event happens once per request or feedback and type. `data` is the same object `GET /v1/requests` or `GET /v1/feedback` answers with, as it was when the event happened, contacts masked. ##### feedback.created ```json { "id": "9e2a4c6b-1d3f-4a5e-8b7c-2e4f6a8c0d10", "type": "feedback.created", "created_at": "2026-11-08T10:13:05Z", "data": { "id": "9e2a4c6b-1d3f-4a5e-8b7c-2e4f6a8c0d10", "location_id": "5b1d0c7e-2f4a-4c3e-9a51-7d2e8f1b6a40", "request_id": "c3f1a9e2-7b4d-4e8a-b6c2-1f9d3e5a7b20", "rating": 2, "text": "We waited 20 minutes past our booking time.", "contact": { "name": "Emma C." }, "status": "new", "created_at": "2026-11-08T10:13:05Z" } } ``` ##### request.rated ```json { "id": "2d4f6a8c-0e1b-4c3d-a5e7-9b1d3f5a7c60", "type": "request.rated", "created_at": "2026-11-08T10:13:05Z", "data": { "id": "c3f1a9e2-7b4d-4e8a-b6c2-1f9d3e5a7b20", "location_id": "5b1d0c7e-2f4a-4c3e-9a51-7d2e8f1b6a40", "channel": "email", "source": "api", "status": "rated", "contact": { "name": "Emma C.", "email": "em***@example.com", "phone": null }, "rating": 5, "created_at": "2026-11-08T10:13:05Z", "sent_at": "2026-11-08T09:30:04Z", "clicked_at": "2026-11-08T10:12:40Z", "rated_at": "2026-11-08T10:13:05Z" } } ``` ##### request.public_click ```json { "id": "2d4f6a8c-0e1b-4c3d-a5e7-9b1d3f5a7c60", "type": "request.public_click", "created_at": "2026-11-08T10:13:05Z", "data": { "id": "c3f1a9e2-7b4d-4e8a-b6c2-1f9d3e5a7b20", "location_id": "5b1d0c7e-2f4a-4c3e-9a51-7d2e8f1b6a40", "channel": "email", "source": "api", "status": "public_click", "contact": { "name": "Emma C.", "email": "em***@example.com", "phone": null }, "rating": 5, "created_at": "2026-11-08T10:13:05Z", "sent_at": "2026-11-08T09:30:04Z", "clicked_at": "2026-11-08T10:12:40Z", "rated_at": "2026-11-08T10:13:05Z" } } ``` ##### request.sent ```json { "id": "2d4f6a8c-0e1b-4c3d-a5e7-9b1d3f5a7c60", "type": "request.sent", "created_at": "2026-11-08T10:13:05Z", "data": { "id": "c3f1a9e2-7b4d-4e8a-b6c2-1f9d3e5a7b20", "location_id": "5b1d0c7e-2f4a-4c3e-9a51-7d2e8f1b6a40", "channel": "email", "source": "api", "status": "sent", "contact": { "name": "Emma C.", "email": "em***@example.com", "phone": null }, "rating": null, "created_at": "2026-11-08T10:13:05Z", "sent_at": "2026-11-08T09:30:04Z", "clicked_at": null, "rated_at": null } } ``` ##### request.failed ```json { "id": "2d4f6a8c-0e1b-4c3d-a5e7-9b1d3f5a7c60", "type": "request.failed", "created_at": "2026-11-08T10:13:05Z", "data": { "id": "c3f1a9e2-7b4d-4e8a-b6c2-1f9d3e5a7b20", "location_id": "5b1d0c7e-2f4a-4c3e-9a51-7d2e8f1b6a40", "channel": "email", "source": "api", "status": "failed", "contact": { "name": "Emma C.", "email": "em***@example.com", "phone": null }, "rating": null, "created_at": "2026-11-08T10:13:05Z", "sent_at": null, "clicked_at": null, "rated_at": null } } ``` #### Check the signature Every delivery is a `POST` with `Content-Type: application/json` and three headers. `Citypicked-Signature` is `t=,v1=`, where `v1` is the HMAC-SHA256 of `.` with the webhook's secret. `Citypicked-Event-Id` and `Citypicked-Event-Type` repeat the body's `id` and `type`. 1. Read the raw body before parsing it 2. Compute the HMAC of `t`, a dot and the raw body with your secret 3. Compare it with `v1` in constant time 4. Refuse a `t` more than 300 seconds away from your clock Node.js: ```js import crypto from "node:crypto"; // rawBody is the request body exactly as it arrived, before JSON.parse. export function verifyCitypicked(header, rawBody, secret) { const parts = Object.fromEntries(header.split(",").map((p) => p.split("="))); const t = Number(parts.t); if (!Number.isInteger(t) || Math.abs(Date.now() / 1000 - t) > 300) return false; const expected = crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex"); const given = String(parts.v1 ?? ""); return given.length === expected.length && crypto.timingSafeEqual(Buffer.from(given), Buffer.from(expected)); } ``` Express: ```js import express from "express"; const app = express(); app.post("/hooks/citypicked", express.raw({ type: "application/json" }), (req, res) => { const rawBody = req.body.toString("utf8"); if (!verifyCitypicked(req.get("Citypicked-Signature") ?? "", rawBody, process.env.CITYPICKED_WEBHOOK_SECRET)) { return res.sendStatus(401); } const event = JSON.parse(rawBody); // Dedupe by event.id: the same event can arrive twice. res.sendStatus(200); }); ``` Python: ```python import hashlib import hmac import time def verify_citypicked(header: str, raw_body: bytes, secret: str) -> bool: parts = dict(p.split("=", 1) for p in header.split(",") if "=" in p) try: t = int(parts.get("t", "")) except ValueError: return False if abs(time.time() - t) > 300: return False expected = hmac.new(secret.encode(), f"{t}.".encode() + raw_body, hashlib.sha256).hexdigest() return hmac.compare_digest(expected, parts.get("v1", "")) ``` Secrets start with `cpwh_`. The examples show a placeholder. ``` secret = cpwh_xxxxxxxx ``` #### Retries Answer with any 2xx within 10 seconds. Anything else, a timeout or a redirect counts as a failure and is tried again after 1 minute, 5 minutes, 30 minutes, 2 hours, 5 hours, 10 hours. That makes 7 tries in about 18 hours. - The same event can arrive more than once and out of order, so dedupe by `id` - After 7 failed tries in a row the webhook is turned off with `disabled_reason` `failing` - An answer of 410 Gone turns it off at once with `disabled_reason` `gone` - The account owner sees a turned-off webhook in the app and can turn it on again, or your tool removes it and subscribes again > Polling with `GET /v1/requests?since=` and `GET /v1/feedback?since=` keeps working next to webhooks, as a fallback. ### MCP for AI assistants Connect Citypicked to Claude, ChatGPT, Claude Code or Cursor. Then ask in plain language, and your assistant sends review requests and reads your results. Source: https://citypicked.com/en/developers/mcp MCP server URL: `https://mcp.citypicked.com/mcp` The server speaks MCP over streamable HTTP. Assistants that sign in use OAuth, so nobody copies a key ([how it works](https://citypicked.com/en/developers/authentication#oauth)). Tools that cannot sign in send an API key as a header. #### Claude 1. On Free, Pro or Max, open **Customize > Connectors > Add custom connector**. On Team and Enterprise, an owner adds it under **Organization settings > Connectors > Add > Custom > Web**, then members press **Connect** 2. Name it `Citypicked` and paste the URL `https://mcp.citypicked.com/mcp`. Leave the OAuth client ID and secret empty. Press **Add** 3. Press **Connect**. Citypicked opens, you sign in, check what Claude may do and press **Allow** #### ChatGPT 1. Open **Plugins > + > Add custom MCP server**. In older menus it is **Settings > Apps & Connectors > Advanced settings > Developer mode**, then **Create**. On Business and Enterprise an admin may need to allow custom servers 2. Name it `Citypicked`, paste the server URL `https://mcp.citypicked.com/mcp` and pick **OAuth** as the authentication. Press **Create** 3. Connect. Citypicked opens, you sign in, check what ChatGPT may do and press **Allow** #### Claude Code Add the server, then run `/mcp` in Claude Code and pick Citypicked to sign in. ```bash claude mcp add --transport http citypicked https://mcp.citypicked.com/mcp ``` Or with an API key instead of signing in ```bash claude mcp add --transport http citypicked https://mcp.citypicked.com/mcp \ --header "Authorization: Bearer cp_live_xxxxxxxx" ``` #### Cursor Add this to `~/.cursor/mcp.json` or to `.cursor/mcp.json` in a project, with your own key. ```json { "mcpServers": { "citypicked": { "url": "https://mcp.citypicked.com/mcp", "headers": { "Authorization": "Bearer cp_live_xxxxxxxx" } } } } ``` #### Other clients Any client with remote MCP servers works the same way. The OpenAI Responses API takes the server as a tool, with your key as a header. ```json { "type": "mcp", "server_label": "citypicked", "server_url": "https://mcp.citypicked.com/mcp", "headers": { "Authorization": "Bearer cp_live_xxxxxxxx" }, "require_approval": "always" } ``` #### Tools | Tool | What it does | | --- | --- | | `get_account` | Checks the connection. Account name, plan and language, the scopes, and whether it may send. | | `list_locations` | Lists the locations it can see, with id, country and review link. | | `get_review_link` | Gets a location's review link and its QR code, as URLs and as an image. | | `send_review_request` | Sends one review request to one customer by SMS or email. It shows you the customer, channel and location first and sends only after you say yes. | | `list_requests` | Lists review requests with status, stars and times. Filter by status, location and date. Contacts come masked. | | `list_feedback` | Lists private feedback from the review page. Filter by status, location and date. | | `get_stats` | Numbers for a range of days. Sent, opened, rated, average stars, clicks to review sites and feedback. | Every tool reads only, except `send_review_request`. It needs `consent: true`, which your assistant may set only when you said the customer agreed, and `confirm: true` after you saw the summary. A retried call within 10 minutes goes out once. #### Example prompts - “Ask Emma Clark for a review by email, emma@example.com. She agreed when she booked.” - “How many reviews did we get last month, and what was the average?” - “Show me this week's private feedback and summarize what people complain about.” - “Give me the QR code for our review page so I can print it for the counter.” - “Which requests from yesterday were opened but not rated yet?” #### Limits and safety - The connection acts as the person who allowed it, with their locations - A read-only connection cannot send; the assistant has to ask for more - Sends count against the same limits and credits as the API - Disconnect any assistant in the app under **Settings > Sending > API** ### CLI Citypicked from the terminal. Send review requests, get review links and QR codes, and read feedback and numbers, by hand or in scripts. Source: https://citypicked.com/en/developers/cli > The `citypicked` package is not on npm yet. This page describes the CLI as it is built. Until it is published, the [REST API](https://citypicked.com/en/developers/api) and [MCP](https://citypicked.com/en/developers/mcp) do the same. #### Set up It needs Node.js 20 or newer. Log in once with an API key, or set it as a variable in scripts. ```bash npx citypicked login # or, in scripts export CITYPICKED_API_KEY=cp_live_xxxxxxxx export CITYPICKED_BASE_URL=https://api.citypicked.com/v1 ``` `login` keeps the key in `~/.config/citypicked/config.json` (on Windows in `%APPDATA%\citypicked`), readable only by you. #### Commands | Command | What it does | | --- | --- | | `citypicked login` | Checks a key and saves it, readable only by you | | `citypicked logout` | Removes the saved key | | `citypicked whoami` | Shows the account, the key's name and scopes, and whether it can send | | `citypicked locations` | Lists your locations with id and review link | | `citypicked link [--location ] [--qr ]` | Shows the review link, or saves its QR code | | `citypicked send --name (--email
\| --phone ) --consent` | Sends one review request and asks before it does, unless you pass --yes | | `citypicked requests [--status ] [--since ] [--location ]` | Lists review requests, newest first | | `citypicked feedback [--status ] [--since ] [--location ]` | Lists private feedback | | `citypicked stats [--from ] [--to ] [--location ]` | Shows the numbers for a range of days, by default the last 30 | | `citypicked webhooks [list] \| add --events \| remove ` | Lists, adds and removes the webhooks of this key | Every command takes `--json` (raw JSON on stdout, errors as JSON on stderr), `--base-url ` and `--help`. Lists take `--limit`, `--cursor` and `--all`. `--since` takes `7d`, `12h`, `2w`, a date or a timestamp. #### Examples ```bash citypicked send --name "Emma Clark" --email emma@example.com --consent # in a script, without asking citypicked send --name "$NAME" --email "$EMAIL" --consent --yes --json citypicked requests --status rated --since 1d --json | jq '.data[].id' citypicked link --qr counter.png citypicked webhooks add https://example.com/hooks/citypicked --events feedback.created,request.rated --json | jq -r .secret ``` Without a terminal, `send` and `webhooks remove` need `--yes`; neither acts silently otherwise. `--consent` confirms the customer agreed to get messages. #### Exit codes | Code | Meaning | | --- | --- | | 0 | Done | | 1 | Unexpected error | | 2 | Wrong usage, or sending not confirmed | | 3 | No API key, or the key was refused | | 4 | Not allowed (key without send, plan without API sending) | | 5 | Not found | | 6 | The API refused the input | | 7 | Rate limited | | 8 | Plan wall, the trial is over or the plan has no access | | 9 | Account closed | | 10 | Server or network error | | 11 | Nothing was sent (duplicate, opted out, no credits) | | 12 | Canceled at the prompt | ### Rate limits and errors How many calls you can make, what an error looks like and what each one means. Source: https://citypicked.com/en/developers/errors #### Rate limits | What | Per key | Per account | | --- | --- | --- | | Reads (every `GET`) | 120 a minute | 600 a minute | | Sends (`POST /v1/requests`, cancel, adding or removing webhooks) | 30 a minute | 60 a minute | | New keys in the app | | 20 an hour | - Up to 100 contacts per `POST /v1/requests`, and a body of at most 256 KB - `send_at` plans up to 30 days ahead - Over a limit the answer is 429 with `Retry-After` in seconds #### Error format Every error is an HTTP status plus a JSON body. `code` is stable; `message` is for people and may change. ```json { "error": { "code": "invalid_request", "message": "Each contact needs an email address for channel email.", "field": "contacts.0.email" } } ``` #### Error codes | Status | Code | Meaning | | --- | --- | --- | | 400 | `invalid_request` | The query or body is not valid. `field` names the wrong one, like `contacts.2.email` | | 401 | `unauthorized` | No key, or the key does not work any more | | 402 | `plan_wall` | The account cannot send right now because the trial is over or the plan has no access. Reading keeps working | | 403 | `forbidden` | The key lacks the `send` scope, or the plan has no API sending | | 403 | `account_closed` | The account is closed. Every endpoint answers this | | 404 | `not_found` | Nothing with this id for this key | | 409 | `idempotency_conflict` | The `Idempotency-Key` was used before with another body | | 409 | `not_cancelable` | The request already went out | | 429 | `rate_limited` | Too many calls. Wait for the seconds in `Retry-After` | | 500 | `server_error` | Something went wrong on our side. Try again; with an `Idempotency-Key` nothing is sent twice | #### Retrying safely - Retry 429 after `Retry-After`, and 500 or a network error after a short wait - Send an `Idempotency-Key` with `POST /v1/requests`. The same key within 24 hours returns the first answer and sends nothing again - Never retry 400, 401, 403 or 404 unchanged A send where some contacts could not go out still answers 201; each contact has its own `outcome` ([quickstart](https://citypicked.com/en/developers/quickstart)). ### Changelog What changed in the API, newest first. Changes within v1 only add things; nothing you use stops working. Source: https://citypicked.com/en/developers/changelog #### 1.2 on 2026-11-08 - Webhooks with `/v1/webhooks`, five events and signed deliveries, so tools get ratings and feedback at once instead of polling #### 1.1 on 2026-11-08 - `send_at` plans a request up to 30 days ahead - `POST /v1/requests/{id}/cancel` cancels a planned request - `GET /v1/me` shows the credit balance - Clear rules for `since` and `status` filters - `Idempotency-Key` per key, with a 409 for a reused key with another body - The plan wall applies to sending only; reading keeps working - More error cases named #### 1.0 on 2026-11-08 - The first version. Account, locations, review links and QR codes, review requests, private feedback, numbers and the OpenAPI document The [OpenAPI document](https://api.citypicked.com/v1/openapi.json) always describes the current version.