{"openapi":"3.1.0","info":{"title":"Upstream CRM API","description":"REST v1 API for Upstream CRM. Authenticate, manage deals, contacts, activities, and pipeline data programmatically. Every endpoint is scoped to the organization encoded in the JWT.","version":"1.0.0","contact":{"name":"Upstream Support","email":"support@upstreamcrm.com","url":"https://upstreamcrm.com/docs"},"license":{"name":"Proprietary","url":"https://upstreamcrm.com/terms"}},"servers":[{"url":"https://app.upstreamcrm.com/api/v1","description":"Production"}],"tags":[{"name":"Authentication","description":"Email + password login that issues a short-lived JWT access token (15 min) and a rotating refresh token (30 days). Refresh tokens are single-use — reuse triggers automatic revocation of every active session for that user."},{"name":"Pipeline","description":"Single endpoint that returns everything the mobile pipeline screen needs in one round-trip: stages with aggregate counts/values, and trimmed deal cards for the chosen pipeline."},{"name":"Deals","description":"Full CRUD plus dedicated transitions for stage moves and won/lost status changes. Soft-deletes via `is_deleted` — deletes are reversible from the web app."},{"name":"Contacts","description":"CRUD over the org's contact directory. Soft-deletes; restore from the web app."},{"name":"Activities","description":"Calls, emails, meetings, messages, tasks, notes, demos and physical visits. Includes dedicated `complete` / `uncomplete` transitions so progress through an activity template advances automatically."},{"name":"Activity Templates","description":"Reusable sequences of activity steps (e.g. \"Enterprise discovery: call → demo → proposal → follow-up\") that can be applied to a deal."},{"name":"Devices","description":"Register and revoke Expo push tokens for the authenticated user. The mobile app calls register after sign-in and on every launch; it calls revoke during sign-out."},{"name":"Deal ↔ Template","description":"Apply, inspect or detach an activity template on a specific deal, and schedule the next step."}],"security":[{"BearerAuth":[]}],"components":{"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"Short-lived JWT access token issued by POST /auth/login or POST /auth/refresh. TTL: 15 minutes. Pass as: `Authorization: Bearer <token>`."}},"schemas":{"ErrorEnvelope":{"type":"object","required":["success","error"],"properties":{"success":{"type":"boolean","const":false},"error":{"type":"string","description":"Human-readable error message"},"code":{"type":"string","description":"Stable machine-readable error code (e.g. `UNAUTHORIZED`).","enum":["UNAUTHORIZED","FORBIDDEN","NOT_FOUND","VALIDATION_ERROR","CONFLICT","RATE_LIMITED","INTERNAL_ERROR"]},"details":{"description":"Optional structured detail (e.g. Zod issues with `path` and `message`)."}},"example":{"success":false,"error":"Validation failed","code":"VALIDATION_ERROR","details":[{"path":"email","message":"Invalid email"}]}}}},"paths":{"/auth/login":{"post":{"tags":["Authentication"],"operationId":"authLogin","summary":"Exchange email + password for an access/refresh token pair","description":"Rate-limited to 10 requests/minute per IP. On success, also persists a hashed copy of the refresh token to `mobile_refresh_tokens` so it can be revoked server-side.","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{}}},"example":{"success":true,"data":{"accessToken":"eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOi...","refreshToken":"eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOi...","user":{"id":"4f5f6...","email":"jane@acme.com","fullName":"Jane Doe","avatarUrl":null},"organization":{"id":"9a1b...","name":"Acme Sales","role":"admin"}}}}}},"400":{"description":"User exists but has no organization memberships.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Email not found or password mismatch (generic message — does not leak which).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Too many requests within the rate-limit window. Retry after the seconds reported in the error message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"description":"Lower-cased automatically.","type":"string","format":"email"},"password":{"description":"Plain password — bcrypt-compared on the server.","type":"string","minLength":1,"maxLength":200},"device_info":{"description":"Optional device label. Defaults to User-Agent.","type":"string","maxLength":500}},"required":["email","password"]},"example":{"email":"jane@acme.com","password":"correct horse battery staple","device_info":"iPhone 15 Pro — iOS 18.2"}}}},"security":[]}},"/auth/refresh":{"post":{"tags":["Authentication"],"operationId":"authRefresh","summary":"Rotate a refresh token; receive a new access/refresh pair","description":"Rate-limited to 30 requests/minute per IP. The presented refresh token is single-use — a successful call marks it as `revoked` and links it to the new token. If a previously-rotated token is presented again, every active session for that user is revoked (reuse-detection theft protection).","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{}}},"example":{"success":true,"data":{"accessToken":"eyJhbGciOiJIUzI1NiJ9...","refreshToken":"eyJhbGciOiJIUzI1NiJ9..."}}}}},"400":{"description":"User no longer belongs to any organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Token invalid, expired, revoked, or reuse detected.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Too many requests within the rate-limit window. Retry after the seconds reported in the error message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"refreshToken":{"description":"The refresh token returned by `/auth/login` or a previous `/auth/refresh`.","type":"string"}},"required":["refreshToken"]},"example":{"refreshToken":"eyJhbGciOiJIUzI1NiJ9.eyJzdWIi..."}}}},"security":[]}},"/auth/logout":{"post":{"tags":["Authentication"],"operationId":"authLogout","summary":"Revoke a refresh token","description":"Idempotent. Always returns 200 — whether the token existed, was already revoked, or was malformed — to prevent enumeration.","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{}}},"example":{"success":true,"data":{"success":true}}}}},"400":{"description":"Request body or query failed Zod schema validation. `details[]` lists each offending field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"refreshToken":{"description":"The refresh token to revoke.","type":"string"}},"required":["refreshToken"]},"example":{"refreshToken":"eyJhbGciOi..."}}}},"security":[]}},"/me":{"get":{"tags":["Authentication"],"operationId":"me","summary":"Current user, current organization, and all memberships","description":"Returns the authenticated user, the org currently encoded in the JWT, and every org the user belongs to (for an org switcher).","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{}}},"example":{"success":true,"data":{"user":{"id":"4f5f6...","email":"jane@acme.com","fullName":"Jane Doe","avatarUrl":null},"organization":{"id":"9a1b...","name":"Acme Sales","role":"admin"},"organizations":[{"id":"9a1b...","name":"Acme Sales","role":"admin"},{"id":"b22f...","name":"Acme Labs","role":"member"}]}}}}},"401":{"description":"Missing, invalid or expired access token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Token is valid but the user is not a member of the target organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}},"security":[{"BearerAuth":[]}]}},"/pipeline":{"get":{"tags":["Pipeline"],"operationId":"pipelineGet","summary":"Pipeline + stages + deal cards for the mobile home screen","description":"Defaults to the first active pipeline if `pipelineId` is omitted. Respects partner scoping (`canViewAllOrgData = false`) automatically.","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{}}},"example":{"success":true,"data":{"pipeline":{"id":"a11c...","name":"Default Pipeline"},"pipelines":[{"id":"a11c...","name":"Default Pipeline"}],"stages":[{"id":"s1","name":"Qualified","orderIndex":1,"color":"#3b82f6","probability":20,"dealCount":4,"totalValue":45000},{"id":"s2","name":"Demo","orderIndex":2,"color":"#a855f7","probability":50,"dealCount":2,"totalValue":32000}],"deals":[{"id":"d1","title":"TechCorp upgrade","value":12000,"currency":"USD","probability":30,"temperature":"hot","stageId":"s1","updatedAt":"2026-05-10T14:22:31.000Z","expectedCloseDate":"2026-06-01T00:00:00.000Z","contact":{"id":"c1","name":"John Reed","phone":"+1...","email":"j@techcorp.com"},"company":{"id":"co1","name":"TechCorp"},"owner":{"id":"u1","name":"Jane Doe","avatarUrl":null},"activityStatus":"upcoming","daysSinceLastActivity":2}]}}}}},"400":{"description":"Request body or query failed Zod schema validation. `details[]` lists each offending field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing, invalid or expired access token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Token is valid but the user is not a member of the target organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}},"parameters":[{"name":"pipelineId","in":"query","required":false,"description":"Pipeline to fetch. Defaults to the first active one in the org.","schema":{"type":"string","format":"uuid"}}],"security":[{"BearerAuth":[]}]}},"/deals":{"get":{"tags":["Deals"],"operationId":"dealsList","summary":"Cursor-paginated deal list with filters","description":"Sort: most recently updated first. Cursor pagination via the last `id` of the previous page.","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{}}},"example":{"success":true,"data":{"items":[{"id":"d1","title":"TechCorp upgrade","value":12000,"currency":"USD","status":"open","temperature":"hot","probability":30,"stageId":"s1","stageName":"Qualified","pipelineId":"a11c...","expectedCloseDate":"2026-06-01T00:00:00.000Z","updatedAt":"2026-05-10T14:22:31.000Z","contact":{"id":"c1","name":"John Reed","phone":"+1...","email":"j@techcorp.com"},"company":{"id":"co1","name":"TechCorp"},"owner":{"id":"u1","name":"Jane Doe","avatarUrl":null}}],"nextCursor":"d1"}}}}},"400":{"description":"Request body or query failed Zod schema validation. `details[]` lists each offending field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing, invalid or expired access token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}},"parameters":[{"name":"pipelineId","in":"query","required":false,"description":"Restrict to one pipeline.","schema":{"type":"string","format":"uuid"}},{"name":"stageId","in":"query","required":false,"description":"Restrict to one stage.","schema":{"type":"string","format":"uuid"}},{"name":"status","in":"query","required":false,"description":"Status filter.","schema":{"type":"string","enum":["open","won","lost"]}},{"name":"ownerId","in":"query","required":false,"description":"Restrict to deals owned by a specific user.","schema":{"type":"string","format":"uuid"}},{"name":"search","in":"query","required":false,"description":"Case-insensitive title/company/contact match.","schema":{"type":"string","minLength":1,"maxLength":200}},{"name":"cursor","in":"query","required":false,"description":"Last deal id from the previous page.","schema":{"type":"string","format":"uuid"}},{"name":"limit","in":"query","required":false,"description":"Default 25.","schema":{"type":"integer","minimum":1,"maximum":100}}],"security":[{"BearerAuth":[]}]},"post":{"tags":["Deals"],"operationId":"dealsCreate","summary":"Create a deal","description":"Required: `title` and `stageId`. If `pipelineId` is omitted, it is inferred from the stage. `templateId` optionally applies an activity template on create.","responses":{"201":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{}}},"example":{"success":true,"data":{"id":"d1","title":"TechCorp upgrade","value":12000,"currency":"USD","status":"open","temperature":"hot","probability":30,"stageId":"s1","stageName":"Qualified","pipelineId":"a11c..."}}}}},"400":{"description":"Request body or query failed Zod schema validation. `details[]` lists each offending field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing, invalid or expired access token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Token is valid but the user is not a member of the target organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"The requested resource does not exist or is not visible to the caller.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"title":{"description":"Deal title.","type":"string","minLength":1,"maxLength":255},"stageId":{"description":"Target pipeline stage.","type":"string","format":"uuid"},"value":{"description":"Deal value in `currency`.","type":"number","minimum":0},"currency":{"description":"ISO currency code, e.g. `USD`, `INR`. Defaults to org currency.","type":"string","minLength":1,"maxLength":10},"pipelineId":{"description":"Inferred from stage if omitted.","type":"string","format":"uuid"},"contactId":{"description":"Primary contact.","type":["string","null"],"format":"uuid"},"companyId":{"description":"Related company.","type":["string","null"],"format":"uuid"},"sourceId":{"description":"Lead source.","type":["string","null"],"format":"uuid"},"partnerId":{"description":"Referring partner.","type":["string","null"],"format":"uuid"},"probability":{"description":"Defaults to stage probability.","type":["integer","null"],"minimum":0,"maximum":100},"expectedCloseDate":{"description":"Target close date.","type":["string","null"],"format":"date-time"},"visibility":{"description":"Default `organization`.","type":"string","enum":["private","team","organization"]},"notes":{"description":"Free-text notes.","type":["string","null"],"maxLength":10000},"temperature":{"description":"Defaults to `cold`.","type":"string","enum":["hot","cold"]},"templateId":{"description":"Apply an activity template after create.","type":["string","null"],"format":"uuid"}},"required":["title","stageId"]},"example":{"title":"TechCorp upgrade","value":12000,"currency":"USD","stageId":"s1","contactId":"c1","companyId":"co1","probability":30,"expectedCloseDate":"2026-06-01T00:00:00.000Z","temperature":"hot"}}}},"security":[{"BearerAuth":[]}]}},"/deals/{id}":{"get":{"tags":["Deals"],"operationId":"dealsGet","summary":"Fetch a single deal with detail fields","description":"Returns the same shape as `deals-list` items plus `notes`, `lostReason`, `visibility`, `createdAt`, `source`, `partner`, and `recentActivityCount`.","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{}}},"example":{"success":true,"data":{"id":"d1","title":"TechCorp upgrade","value":12000,"currency":"USD","status":"open","temperature":"hot","probability":30,"stageId":"s1","stageName":"Qualified","pipelineId":"a11c...","notes":"Spoke with John — needs procurement sign-off.","lostReason":null,"visibility":"organization","createdAt":"2026-04-12T09:01:00.000Z","source":{"id":"src1","name":"Inbound — website"},"partner":null,"recentActivityCount":4}}}}},"401":{"description":"Missing, invalid or expired access token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"The requested resource does not exist or is not visible to the caller.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}},"parameters":[{"name":"id","in":"path","required":true,"description":"Deal id.","schema":{"type":"string","format":"uuid"}}],"security":[{"BearerAuth":[]}]},"patch":{"tags":["Deals"],"operationId":"dealsUpdate","summary":"Partially update a deal","description":"All fields optional. Set a value to `null` to clear it. `status` is **not** mutable here — use `/deals/{id}/status`. `stageId` is **not** mutable here — use `/deals/{id}/stage`.","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{}}},"example":{"success":true,"data":{"id":"d1","value":15000,"probability":50,"temperature":"hot"}}}}},"400":{"description":"Request body or query failed Zod schema validation. `details[]` lists each offending field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing, invalid or expired access token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Token is valid but the user is not a member of the target organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"The requested resource does not exist or is not visible to the caller.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}},"parameters":[{"name":"id","in":"path","required":true,"description":"Deal id.","schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"title":{"description":"","type":"string","minLength":1,"maxLength":255},"value":{"description":"","type":"number","minimum":0},"currency":{"description":"","type":"string"},"contactId":{"description":"","type":["string","null"],"format":"uuid"},"companyId":{"description":"","type":["string","null"],"format":"uuid"},"sourceId":{"description":"","type":["string","null"],"format":"uuid"},"partnerId":{"description":"","type":["string","null"],"format":"uuid"},"probability":{"description":"","type":["integer","null"],"minimum":0,"maximum":100},"expectedCloseDate":{"description":"","type":["string","null"],"format":"date-time"},"visibility":{"description":"","type":"string","enum":["private","team","organization"]},"notes":{"description":"","type":["string","null"],"maxLength":10000},"temperature":{"description":"","type":"string","enum":["hot","cold"]}}},"example":{"value":15000,"probability":50,"temperature":"hot"}}}},"security":[{"BearerAuth":[]}]},"delete":{"tags":["Deals"],"operationId":"dealsDelete","summary":"Soft-delete a deal","description":"Sets `is_deleted = true`. The deal can be restored from the web app `Deals → Deleted` view.","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{}}},"example":{"success":true,"data":{"deleted":true}}}}},"401":{"description":"Missing, invalid or expired access token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Token is valid but the user is not a member of the target organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"The requested resource does not exist or is not visible to the caller.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}},"parameters":[{"name":"id","in":"path","required":true,"description":"Deal id.","schema":{"type":"string","format":"uuid"}}],"security":[{"BearerAuth":[]}]}},"/deals/{id}/stage":{"patch":{"tags":["Deals"],"operationId":"dealsStage","summary":"Move a deal to another stage","description":"Records a row in `deal_stage_history` and logs a `deal_stage_changed` activity. Will refuse to move into a Won/Lost protected stage — use `/deals/{id}/status` instead.","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{}}},"example":{"success":true,"data":{"id":"d1","stageId":"s2","stageName":"Demo"}}}}},"400":{"description":"Request body or query failed Zod schema validation. `details[]` lists each offending field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing, invalid or expired access token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Token is valid but the user is not a member of the target organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"The requested resource does not exist or is not visible to the caller.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}},"parameters":[{"name":"id","in":"path","required":true,"description":"Deal id.","schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"stageId":{"description":"Destination stage (must belong to the same pipeline).","type":"string","format":"uuid"}},"required":["stageId"]},"example":{"stageId":"s2"}}}},"security":[{"BearerAuth":[]}]}},"/deals/{id}/status":{"patch":{"tags":["Deals"],"operationId":"dealsStatus","summary":"Mark a deal as won, lost, or re-open it","description":"`lostReason` is required when `status = lost`. Pass `cancelRemainingOnLost = true` to auto-cancel open activity-template steps on loss.","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{}}},"example":{"success":true,"data":{"id":"d1","status":"won","stageId":"won-stage-id"}}}}},"400":{"description":"Request body or query failed Zod schema validation. `details[]` lists each offending field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing, invalid or expired access token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Token is valid but the user is not a member of the target organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"The requested resource does not exist or is not visible to the caller.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}},"parameters":[{"name":"id","in":"path","required":true,"description":"Deal id.","schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"status":{"description":"New status.","type":"string","enum":["open","won","lost"]},"lostReason":{"description":"Required when `status = lost`.","type":"string","maxLength":500},"cancelRemainingOnLost":{"description":"Cancel open template steps if losing.","type":"boolean"}},"required":["status"]},"example":{"status":"won"}}}},"security":[{"BearerAuth":[]}]}},"/contacts":{"get":{"tags":["Contacts"],"operationId":"contactsList","summary":"Cursor-paginated contact list","description":"Free-text search matches first name, last name, email, and phone.","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{}}},"example":{"success":true,"data":{"items":[{"id":"c1","firstName":"John","lastName":"Reed","email":"j@techcorp.com","phone":"+1...","jobTitle":"VP Eng","company":{"id":"co1","name":"TechCorp"}}],"nextCursor":"c1"}}}}},"400":{"description":"Request body or query failed Zod schema validation. `details[]` lists each offending field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing, invalid or expired access token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}},"parameters":[{"name":"search","in":"query","required":false,"description":"Free-text search.","schema":{"type":"string","minLength":1,"maxLength":200}},{"name":"companyId","in":"query","required":false,"description":"Filter by company.","schema":{"type":"string","format":"uuid"}},{"name":"ownerId","in":"query","required":false,"description":"Filter by owner.","schema":{"type":"string","format":"uuid"}},{"name":"cursor","in":"query","required":false,"description":"Last contact id from prior page.","schema":{"type":"string","format":"uuid"}},{"name":"limit","in":"query","required":false,"description":"Default 25.","schema":{"type":"integer","minimum":1,"maximum":100}}],"security":[{"BearerAuth":[]}]},"post":{"tags":["Contacts"],"operationId":"contactsCreate","summary":"Create a contact","description":"Only `firstName` is required.","responses":{"201":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{}}},"example":{"success":true,"data":{"id":"c1","firstName":"John","lastName":"Reed","email":"j@techcorp.com","company":{"id":"co1","name":"TechCorp"}}}}}},"400":{"description":"Request body or query failed Zod schema validation. `details[]` lists each offending field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing, invalid or expired access token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Unique constraint violation (e.g. duplicate email on contact create).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"firstName":{"description":"","type":"string","minLength":1,"maxLength":100},"lastName":{"description":"","type":"string","maxLength":100},"email":{"description":"","type":["string","null"],"format":"email"},"phone":{"description":"Stored as-entered.","type":["string","null"],"maxLength":50},"jobTitle":{"description":"","type":["string","null"],"maxLength":100},"companyId":{"description":"","type":["string","null"],"format":"uuid"},"address":{"description":"","type":["string","null"],"maxLength":500},"city":{"description":"","type":["string","null"],"maxLength":100},"state":{"description":"","type":["string","null"],"maxLength":100},"country":{"description":"","type":["string","null"],"maxLength":100},"postalCode":{"description":"","type":["string","null"],"maxLength":20},"notes":{"description":"","type":["string","null"],"maxLength":10000}},"required":["firstName"]},"example":{"firstName":"John","lastName":"Reed","email":"j@techcorp.com","companyId":"co1","jobTitle":"VP Eng"}}}},"security":[{"BearerAuth":[]}]}},"/contacts/{id}":{"get":{"tags":["Contacts"],"operationId":"contactsGet","summary":"Fetch one contact","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{}}},"example":{"success":true,"data":{"id":"c1","firstName":"John","lastName":"Reed","email":"j@techcorp.com","phone":"+1...","jobTitle":"VP Eng","company":{"id":"co1","name":"TechCorp"},"address":"...","city":"San Francisco","country":"USA","notes":null}}}}},"401":{"description":"Missing, invalid or expired access token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"The requested resource does not exist or is not visible to the caller.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}},"parameters":[{"name":"id","in":"path","required":true,"description":"Contact id.","schema":{"type":"string","format":"uuid"}}],"security":[{"BearerAuth":[]}]},"patch":{"tags":["Contacts"],"operationId":"contactsUpdate","summary":"Partially update a contact","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{}}},"example":{"success":true,"data":{"id":"c1","jobTitle":"CTO","phone":"+1 415 555 0144"}}}}},"400":{"description":"Request body or query failed Zod schema validation. `details[]` lists each offending field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing, invalid or expired access token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"The requested resource does not exist or is not visible to the caller.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}},"parameters":[{"name":"id","in":"path","required":true,"description":"Contact id.","schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"firstName":{"description":"","type":"string","minLength":1,"maxLength":100},"lastName":{"description":"","type":"string","maxLength":100},"email":{"description":"","type":["string","null"],"format":"email"},"phone":{"description":"","type":["string","null"]},"jobTitle":{"description":"","type":["string","null"]},"companyId":{"description":"","type":["string","null"],"format":"uuid"},"address":{"description":"","type":["string","null"]},"city":{"description":"","type":["string","null"]},"state":{"description":"","type":["string","null"]},"country":{"description":"","type":["string","null"]},"postalCode":{"description":"","type":["string","null"]},"notes":{"description":"","type":["string","null"]}}},"example":{"jobTitle":"CTO","phone":"+1 415 555 0144"}}}},"security":[{"BearerAuth":[]}]},"delete":{"tags":["Contacts"],"operationId":"contactsDelete","summary":"Soft-delete a contact","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{}}},"example":{"success":true,"data":{"deleted":true}}}}},"401":{"description":"Missing, invalid or expired access token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Token is valid but the user is not a member of the target organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"The requested resource does not exist or is not visible to the caller.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}},"parameters":[{"name":"id","in":"path","required":true,"description":"Contact id.","schema":{"type":"string","format":"uuid"}}],"security":[{"BearerAuth":[]}]}},"/activities":{"get":{"tags":["Activities"],"operationId":"activitiesList","summary":"Cursor-paginated activity list with rich filters","description":"`status` filters by computed activity status (overdue, due today, upcoming, completed, open). Date filters operate on `due_date`.","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{}}},"example":{"success":true,"data":{"items":[{"id":"a1","type":"call","title":"Follow-up call with John","description":null,"dueDate":"2026-05-13T15:00:00.000Z","completedAt":null,"durationMinutes":30,"status":"upcoming","assignee":{"id":"u1","name":"Jane Doe","avatarUrl":null},"deal":{"id":"d1","title":"TechCorp upgrade"},"contact":{"id":"c1","name":"John Reed"}}],"nextCursor":"a1"}}}}},"400":{"description":"Request body or query failed Zod schema validation. `details[]` lists each offending field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing, invalid or expired access token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}},"parameters":[{"name":"type","in":"query","required":false,"description":"Activity type.","schema":{"type":"string","enum":["call","email","meeting","message","task","note","demo","physical_visit"]}},{"name":"status","in":"query","required":false,"description":"Computed status.","schema":{"type":"string","enum":["open","completed","overdue","due_today","upcoming"]}},{"name":"assigneeId","in":"query","required":false,"description":"Filter by assignee.","schema":{"type":"string","format":"uuid"}},{"name":"dealId","in":"query","required":false,"description":"Activities for a single deal.","schema":{"type":"string","format":"uuid"}},{"name":"contactId","in":"query","required":false,"description":"Activities for a single contact.","schema":{"type":"string","format":"uuid"}},{"name":"dateFrom","in":"query","required":false,"description":"Inclusive lower bound on due date.","schema":{"type":"string","format":"date-time"}},{"name":"dateTo","in":"query","required":false,"description":"Inclusive upper bound on due date.","schema":{"type":"string","format":"date-time"}},{"name":"cursor","in":"query","required":false,"description":"Last activity id from prior page.","schema":{"type":"string","format":"uuid"}},{"name":"limit","in":"query","required":false,"description":"Default 25.","schema":{"type":"integer","minimum":1,"maximum":100}}],"security":[{"BearerAuth":[]}]},"post":{"tags":["Activities"],"operationId":"activitiesCreate","summary":"Create an activity","description":"Pass `completedAt` to create an activity that's already completed (e.g. logging a call after the fact).","responses":{"201":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{}}},"example":{"success":true,"data":{"id":"a1","type":"call","title":"Follow-up call with John","status":"upcoming"}}}}},"400":{"description":"Request body or query failed Zod schema validation. `details[]` lists each offending field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing, invalid or expired access token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Token is valid but the user is not a member of the target organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"The requested resource does not exist or is not visible to the caller.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"type":{"description":"See enum on `GET /activities`.","type":"string"},"title":{"description":"","type":"string","minLength":1,"maxLength":255},"description":{"description":"","type":["string","null"],"maxLength":10000},"dueDate":{"description":"","type":["string","null"],"format":"date-time"},"assigneeId":{"description":"Defaults to caller.","type":["string","null"],"format":"uuid"},"dealId":{"description":"","type":["string","null"],"format":"uuid"},"contactId":{"description":"","type":["string","null"],"format":"uuid"},"durationMinutes":{"description":"For calls / meetings.","type":["integer","null"],"minimum":1},"completedAt":{"description":"Set to log a past activity as already done.","type":["string","null"],"format":"date-time"}},"required":["type","title"]},"example":{"type":"call","title":"Follow-up call with John","dealId":"d1","contactId":"c1","durationMinutes":30,"dueDate":"2026-05-13T15:00:00.000Z"}}}},"security":[{"BearerAuth":[]}]}},"/activities/{id}":{"patch":{"tags":["Activities"],"operationId":"activitiesUpdate","summary":"Partially update an activity","description":"Cannot toggle `completed` here — use the dedicated complete / uncomplete endpoints so template progress stays consistent.","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{}}},"example":{"success":true,"data":{"id":"a1","dueDate":"2026-05-14T15:00:00.000Z","durationMinutes":45}}}}},"400":{"description":"Request body or query failed Zod schema validation. `details[]` lists each offending field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing, invalid or expired access token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Token is valid but the user is not a member of the target organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"The requested resource does not exist or is not visible to the caller.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}},"parameters":[{"name":"id","in":"path","required":true,"description":"Activity id.","schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"type":{"description":"","type":"string"},"title":{"description":"","type":"string","minLength":1,"maxLength":255},"description":{"description":"","type":["string","null"]},"dueDate":{"description":"","type":["string","null"],"format":"date-time"},"assigneeId":{"description":"","type":["string","null"],"format":"uuid"},"dealId":{"description":"","type":["string","null"],"format":"uuid"},"contactId":{"description":"","type":["string","null"],"format":"uuid"},"durationMinutes":{"description":"","type":["integer","null"],"minimum":1}}},"example":{"dueDate":"2026-05-14T15:00:00.000Z","durationMinutes":45}}}},"security":[{"BearerAuth":[]}]},"delete":{"tags":["Activities"],"operationId":"activitiesDelete","summary":"Delete an activity","description":"If the activity was part of a deal template, the response includes the next pending step (if any) so the UI can update.","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{}}},"example":{"success":true,"data":{"deleted":true,"nextPendingStep":{"stepIndex":2,"type":"email","title":"Send proposal"}}}}}},"401":{"description":"Missing, invalid or expired access token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Token is valid but the user is not a member of the target organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"The requested resource does not exist or is not visible to the caller.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}},"parameters":[{"name":"id","in":"path","required":true,"description":"Activity id.","schema":{"type":"string","format":"uuid"}}],"security":[{"BearerAuth":[]}]}},"/activities/{id}/complete":{"post":{"tags":["Activities"],"operationId":"activitiesComplete","summary":"Mark an activity as complete","description":"Sets `completed_at`. If the activity is a template step, advances the deal's template progress.","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{}}},"example":{"success":true,"data":{"id":"a1","completedAt":"2026-05-12T10:31:00.000Z","nextPendingStep":{"stepIndex":2,"type":"email","title":"Send proposal"}}}}}},"401":{"description":"Missing, invalid or expired access token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Token is valid but the user is not a member of the target organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"The requested resource does not exist or is not visible to the caller.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}},"parameters":[{"name":"id","in":"path","required":true,"description":"Activity id.","schema":{"type":"string","format":"uuid"}}],"security":[{"BearerAuth":[]}]}},"/activities/{id}/uncomplete":{"post":{"tags":["Activities"],"operationId":"activitiesUncomplete","summary":"Re-open a completed activity","description":"Clears `completed_at`. Rolls back template progress if applicable.","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{}}},"example":{"success":true,"data":{"id":"a1","completedAt":null}}}}},"401":{"description":"Missing, invalid or expired access token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Token is valid but the user is not a member of the target organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"The requested resource does not exist or is not visible to the caller.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}},"parameters":[{"name":"id","in":"path","required":true,"description":"Activity id.","schema":{"type":"string","format":"uuid"}}],"security":[{"BearerAuth":[]}]}},"/activity-templates":{"get":{"tags":["Activity Templates"],"operationId":"activityTemplatesList","summary":"List all activity templates in the organization","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{}}},"example":{"success":true,"data":[{"id":"t1","name":"Enterprise discovery","description":"Standard 4-step discovery sequence","steps":[{"id":"st1","orderIndex":1,"type":"call","title":"Discovery call"},{"id":"st2","orderIndex":2,"type":"demo","title":"Product demo"}],"archivedAt":null}]}}}},"401":{"description":"Missing, invalid or expired access token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}},"parameters":[{"name":"includeArchived","in":"query","required":false,"description":"Include archived templates. Default `false`.","schema":{"type":"boolean"}}],"security":[{"BearerAuth":[]}]},"post":{"tags":["Activity Templates"],"operationId":"activityTemplatesCreate","summary":"Create an activity template","description":"Each step requires `type` and `title`.","responses":{"201":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{}}},"example":{"success":true,"data":{"id":"t1","name":"Enterprise discovery"}}}}},"400":{"description":"Request body or query failed Zod schema validation. `details[]` lists each offending field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing, invalid or expired access token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Token is valid but the user is not a member of the target organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"description":"","type":"string","minLength":1,"maxLength":255},"description":{"description":"","type":["string","null"],"maxLength":2000}},"required":["name"]},"example":{"name":"Enterprise discovery","description":"Standard 4-step discovery","steps":[{"type":"call","title":"Discovery call"},{"type":"demo","title":"Product demo"},{"type":"email","title":"Send proposal"},{"type":"task","title":"Internal review"}]}}}},"security":[{"BearerAuth":[]}]}},"/activity-templates/{id}":{"get":{"tags":["Activity Templates"],"operationId":"activityTemplatesGet","summary":"Fetch one activity template","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{}}},"example":{"success":true,"data":{"id":"t1","name":"Enterprise discovery","steps":[]}}}}},"401":{"description":"Missing, invalid or expired access token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"The requested resource does not exist or is not visible to the caller.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}},"parameters":[{"name":"id","in":"path","required":true,"description":"Template id.","schema":{"type":"string","format":"uuid"}}],"security":[{"BearerAuth":[]}]},"patch":{"tags":["Activity Templates"],"operationId":"activityTemplatesUpdate","summary":"Update a template (incl. replace steps)","description":"If `steps` is provided, the template's step list is fully replaced. Existing step `id`s present in the body are preserved; missing ones are deleted; new entries are inserted.","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{}}},"example":{"success":true,"data":{"id":"t1","name":"Enterprise discovery v2"}}}}},"400":{"description":"Request body or query failed Zod schema validation. `details[]` lists each offending field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing, invalid or expired access token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Token is valid but the user is not a member of the target organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"The requested resource does not exist or is not visible to the caller.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}},"parameters":[{"name":"id","in":"path","required":true,"description":"Template id.","schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"description":"","type":"string","minLength":1,"maxLength":255},"description":{"description":"","type":["string","null"]},"steps":{"description":"Full step list. Existing step ids preserved; omitted ones deleted.","type":"array","items":{}}}},"example":{"name":"Enterprise discovery v2","steps":[{"id":"st1","type":"call","title":"Discovery call"},{"type":"meeting","title":"Solution workshop"}]}}}},"security":[{"BearerAuth":[]}]},"delete":{"tags":["Activity Templates"],"operationId":"activityTemplatesDelete","summary":"Archive a template","description":"Soft-archive; existing deals already on this template are unaffected.","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{}}},"example":{"success":true,"data":{"deleted":true}}}}},"401":{"description":"Missing, invalid or expired access token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Token is valid but the user is not a member of the target organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"The requested resource does not exist or is not visible to the caller.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}},"parameters":[{"name":"id","in":"path","required":true,"description":"Template id.","schema":{"type":"string","format":"uuid"}}],"security":[{"BearerAuth":[]}]}},"/devices/register":{"post":{"tags":["Devices"],"operationId":"devicesRegister","summary":"Register / refresh an Expo push token for this user","description":"Idempotent upsert keyed on `expoPushToken`. Re-registering the same token on every app launch is cheap and recommended — it refreshes `last_seen_at` and re-activates a previously-revoked row.","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{}}},"example":{"success":true,"data":{"id":"7c1a...","expoPushToken":"ExponentPushToken[xxxxxxxxxxxxxxxxxxxxxx]","platform":"ios","createdAt":"2026-05-12T10:00:00.000Z"}}}}},"400":{"description":"The supplied token does not match the Expo push token format.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing, invalid or expired access token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"expoPushToken":{"description":"The Expo push token returned by `Notifications.getExpoPushTokenAsync()`. Must start with `ExponentPushToken[` or `ExpoPushToken[`.","type":"string","minLength":10,"maxLength":255},"platform":{"description":"Device platform.","type":"string","enum":["ios","android"]},"deviceInfo":{"description":"Optional device label. Defaults to User-Agent.","type":"string","maxLength":500}},"required":["expoPushToken","platform"]},"example":{"expoPushToken":"ExponentPushToken[xxxxxxxxxxxxxxxxxxxxxx]","platform":"ios","deviceInfo":"iPhone 15 Pro — iOS 18.2"}}}},"security":[{"BearerAuth":[]}]},"delete":{"tags":["Devices"],"operationId":"devicesUnregister","summary":"Revoke a push token (called on sign-out)","description":"Idempotent. Returns 200 whether the token existed or not.","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{}}},"example":{"success":true,"data":{"revoked":true}}}}},"400":{"description":"Request body or query failed Zod schema validation. `details[]` lists each offending field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing, invalid or expired access token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"expoPushToken":{"description":"The token to revoke. Must belong to the calling user.","type":"string","minLength":10,"maxLength":255}},"required":["expoPushToken"]},"example":{"expoPushToken":"ExponentPushToken[xxxxxxxxxxxxxxxxxxxxxx]"}}}},"security":[{"BearerAuth":[]}]}},"/deals/{id}/template":{"get":{"tags":["Deal ↔ Template"],"operationId":"dealTemplateProgress","summary":"Current template progress on a deal","description":"Returns the template (if any) and the status of each step.","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{}}},"example":{"success":true,"data":{"template":{"id":"t1","name":"Enterprise discovery"},"steps":[{"stepIndex":1,"type":"call","title":"Discovery call","status":"completed","activityId":"a1"},{"stepIndex":2,"type":"demo","title":"Product demo","status":"pending","activityId":null}],"nextPendingStep":{"stepIndex":2,"type":"demo","title":"Product demo"}}}}}},"401":{"description":"Missing, invalid or expired access token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"The requested resource does not exist or is not visible to the caller.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}},"parameters":[{"name":"id","in":"path","required":true,"description":"Deal id.","schema":{"type":"string","format":"uuid"}}],"security":[{"BearerAuth":[]}]},"put":{"tags":["Deal ↔ Template"],"operationId":"dealTemplateSet","summary":"Attach or detach a template from a deal","description":"Pass `templateId: null` to detach the current template.","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{}}},"example":{"success":true,"data":{"templateId":"t1","nextPendingStep":{"stepIndex":1,"type":"call","title":"Discovery call"}}}}}},"400":{"description":"Request body or query failed Zod schema validation. `details[]` lists each offending field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing, invalid or expired access token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Token is valid but the user is not a member of the target organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"The requested resource does not exist or is not visible to the caller.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}},"parameters":[{"name":"id","in":"path","required":true,"description":"Deal id.","schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"templateId":{"description":"Template to apply, or `null` to detach.","type":["string","null"],"format":"uuid"}},"required":["templateId"]},"example":{"templateId":"t1"}}}},"security":[{"BearerAuth":[]}]}},"/deals/{id}/template/schedule-next":{"post":{"tags":["Deal ↔ Template"],"operationId":"dealTemplateScheduleNext","summary":"Schedule the next pending template step as an activity","description":"Creates the activity for the deal's next pending step, using the supplied `dueDate`.","responses":{"201":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{}}},"example":{"success":true,"data":{"activity":{"id":"a2","type":"demo","title":"Product demo","dueDate":"2026-05-15T14:00:00.000Z"},"nextPendingStep":{"stepIndex":3,"type":"email","title":"Send proposal"}}}}}},"400":{"description":"Request body or query failed Zod schema validation. `details[]` lists each offending field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing, invalid or expired access token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Token is valid but the user is not a member of the target organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"The requested resource does not exist or is not visible to the caller.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}},"parameters":[{"name":"id","in":"path","required":true,"description":"Deal id.","schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"dueDate":{"description":"When the next step should be due.","type":"string","format":"date-time"},"notes":{"description":"Optional notes on the new activity.","type":["string","null"],"maxLength":10000}},"required":["dueDate"]},"example":{"dueDate":"2026-05-15T14:00:00.000Z"}}}},"security":[{"BearerAuth":[]}]}}}}