# Mendly Mendly is a home-services and events marketplace that turns a plain-language problem description into a matched, vetted professional. Instead of forms and dropdowns, you describe what is wrong ("my kitchen sink is leaking under the cabinet"), optionally attach a photo or video, and Mendly classifies the service type, urgency and location, then matches a provider whose real calendar shows them free at that time. Mendly is built to be operated by software as well as by people. ## Agent API - Base URL: https://mendly.services/api/v1 - Authentication: Bearer token (API key). Request access at https://mendly.services/developers - OpenAPI 3.1 description: https://mendly.services/api/v1/openapi.json - Plugin manifest: https://mendly.services/.well-known/ai-plugin.json ## Endpoints POST /api/v1/agent/requests Create a service request from a natural-language description. Headers: Authorization: Bearer Idempotency-Key: Body: { "problem": "", "urgency"?: "low|medium|high|emergency", "hints"?: { "service_type", "location", "contact_name", "contact_email", "contact_phone", "preferred_time" }, "media_refs"?: ["..."], "webhook_url"?: "..." } Returns: { request_id, job_draft_id, status, classified_service_type, classified_category, intent, urgency, location, confidence, agent_originated } POST /api/v1/agent/requests/status?id= Current state of a request. Headers: Authorization: Bearer Returns: { request_id, status, job_draft_id, job_status, workflow_state, provider_assigned, created_at, updated_at } Note the method. This endpoint is POST, not GET. This file previously documented it as GET in two places while the OpenAPI description and the running route were POST, so an agent that trusted this file failed on its first status check with a 405 and no explanation of why. ## Scopes Every key carries scopes. A call outside them returns 403 naming the scope required, so you can tell "this key cannot do that" from "that failed". requests:write create service requests requests:read read request status catalog:read services and categories providers:read professionals who can take work providers:write edit a professional jobs:write accept, decline, schedule, cancel admin:read operating numbers and dispatch health admin:write approve, delete, change platform settings admin:write is never granted by default. Every scoped call is recorded server-side, by the API rather than by the caller, so an agent cannot act without leaving a record. ## Operating the platform GET /api/v1/catalog Scope: catalog:read. What Mendly covers. Use it to answer "can Mendly help with this" before submitting rather than after. GET /api/v1/providers?service=&limit= Scope: providers:read. Professionals who can actually take work: approved, listed and not paused. A wider list becomes a promise Mendly cannot keep, so include_unavailable=true is separate and needs admin:read as well. Contact details are never returned. GET /api/v1/admin/overview Scope: admin:read. Counts plus health.can_dispatch. Read that field. Zero dispatchable professionals and a quiet Tuesday produce identical job counts, and only one of them means every new request will stall. PATCH /api/v1/admin/providers/ Scope: admin:write. Allow-listed fields only: visibility, onboarding_status, do_not_book, business_name, phone, bio. Anything else is refused by name. DELETE /api/v1/admin/providers/ Scope: admin:write. Removes the login, the email address and every job, booking, appointment, message and calendar connection. Irreversible. The route re-checks and returns 500 rather than reporting a delete that did not happen. GET /api/v1/admin/customers?q=&status=&limit=&offset= Scope: admin:read. Paginated, limit defaults to 50 and caps at 200. Returns the same sane default field set the admin dashboard's customer table shows (id, full_name, email, phone, city, state, zip_code, status, customer_type, property_type, created_at), not the full profile. q matches against name, email and phone. GET /api/v1/admin/customers/ Scope: admin:read. The full profile for one customer, including address, notes, property details and AI summary. PATCH /api/v1/admin/customers/ Scope: admin:write. Allow-listed fields only: full_name, email, phone, address, city, state, zip_code, status, notes, customer_type, property_type, square_footage, room_count, preferred_contact_method, urgency_preference, ai_summary. Anything else is refused by name. DELETE /api/v1/admin/customers/ Scope: admin:write. Removes the login, the email address and every job, booking, appointment and message tied to the customer. Irreversible. The route re-checks and returns 500 rather than reporting a delete that did not happen. GET /api/v1/admin/settings Scope: admin:read. Reads only the agent-editable subset of platform settings, not the full admin settings surface: require_pro_verification, require_event_vendor_verification, require_provider_approval, auto_assign_providers, allow_customer_self_booking, platform_fee_percentage, provider_payout_percentage, dispatch_default_radius_miles. PATCH /api/v1/admin/settings Scope: admin:write. Same eight fields, allow-listed, anything else refused by name. This can change professional and event-vendor approval requirements, provider auto-assignment, self-booking, the platform fee and payout split, and the default dispatch radius, platform-wide, immediately. Deliberately excluded and not settable through this API at all: the external AI provider's API key (a secret), the webhook destination (an agent that could redirect it could exfiltrate through it), the payment processor choice (structural, not a toggle, and wrong values break payment processing), and the settings that govern the automation layer's own oversight (require_human_approval, rate_limit_ai_actions, max_ai_jobs_per_day, ai_operating_mode) — an agent should not be able to widen its own leash. ## Acting on a job POST /api/v1/jobs//respond Scope: jobs:write. Accept or decline on a professional's behalf. Body: { "action": "accepted" | "declined", "acting_provider_id": "", "decline_reason"?: "..." } acting_provider_id is required and is not optional shorthand. An API key is not a person, so there is no "current professional" to infer, and on the other end of an acceptance is somebody who will be expected to turn up. The job must already be assigned to that professional or the call is refused. Answering a job that already has a different answer returns 409 with the existing answer rather than overwriting it. Repeating the same answer succeeds, so retries and duplicate webhook deliveries are safe. POST /api/v1/jobs//cancel Scope: jobs:write. Body: { "reason"?: "...", "cancelled_by"?: "customer" | "provider" | "platform" } Also removes the event from the professional's connected calendar. Read calendar_event_removed in the response. If it is false, the slot is still blocked in their calendar and they must delete it by hand, or they will quietly stop being offered work in that window without ever knowing why. Cancelling an already-cancelled job returns success, not an error. POST /api/v1/jobs//schedule Scope: jobs:write. Propose a time for a job the professional has accepted. Body: { "date": "YYYY-MM-DD", "time": "HH:MM", "acting_provider_id": "", "duration_minutes"?: 120, "note"?: "..." } Propose, not confirm. This endpoint cannot book a time on the customer's behalf and that is deliberate: a customer who arranged a visit did not agree to let software decide when a stranger arrives at their home. The proposal goes to them and they accept it. A time the professional is not free for is refused with 409 and the conflicting periods. Both Mendly's own bookings and their connected calendar are checked. Do not retry the same slot; pick another. If Mendly cannot read its own bookings it returns 503 rather than claiming anyone is free. If an external calendar is unreachable the call proceeds and says so in availability_checked.external_errors, which means the slot was cleared against a weaker guarantee than usual. ## What you must supply A job cannot be dispatched to a customer Mendly cannot reach. Include hints.location, and either hints.contact_email or hints.contact_phone. Without them the request is created and returns 201, then stalls with no professional ever assigned. It looks like success and is not. ## What Mendly will not do without a person - Take payment. Card details are never accepted through this API. - Confirm an appointment time on the customer's behalf. A proposed time is offered to the customer for them to accept. - Approve or list a professional. Professionals sign themselves up. Do not present any of the above to a user as something you can complete. ## Retries and duplicates Send an Idempotency-Key on create. Retrying with the same key returns the original request rather than booking a second job against the same problem. A 429 means the key's rate limit is exceeded: back off and retry the same call. Do not re-submit as a new request, which creates a duplicate job. ## Docs and contact Developer documentation with worked examples: https://mendly.services/developers Anything else: partnerships@mendly.services