> ## Documentation Index
> Fetch the complete documentation index at: https://docs.recurso.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Sync CRM Now

> Run the workspace's CRM contact sweep synchronously to verify a freshly connected HubSpot token instead of waiting for the daily sweep.

Runs the CRM sweep for the calling workspace synchronously and reports how many
customers were upserted as CRM contacts. It exists to verify a connection just
stored with [Connect an Integration](/api-reference/integration-connections/create)
(`category: crm`, `provider: hubspot`) without waiting for the daily sweep — so
one call is capped at 25 contacts. Anything left over is reported in
`contacts_remaining` and finished by the daily sweep. When the provider
rejects every contact (bad token, missing scopes) — or rejects the custom
property bootstrap that runs first — the call fails with a `424` carrying the
provider's own error. The operation description shown above says `502`
for that case; the API deliberately answers `424` instead — the edge proxy in
front of the hosted API replaces origin `502` responses with its own HTML
error page, which would swallow the provider's message — and the spec's
response list already declares `424`. When at least one contact was upserted,
the call returns `200` and any individual contacts the provider rejected are
logged server-side only; they are counted in neither `contacts_synced` nor
`contacts_remaining`, and the daily sweep retries them.

## Example Request

```bash theme={null}
curl -X POST "https://api.recurso.dev/v1/crm/sync" \
  -H "Authorization: Bearer $API_KEY"
```

## Response

```json theme={null}
{
  "data": {
    "contacts_synced": 25,
    "contacts_remaining": 112
  }
}
```

## Fields

| Field                | Type    | Description                                                                                                                                            |
| -------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `contacts_synced`    | integer | Contacts upserted in the CRM by this call (at most 25)                                                                                                 |
| `contacts_remaining` | integer | Eligible contacts left unattempted once the 25-contact cap was reached; the daily sweep finishes them. Contacts the provider rejected are not included |

## Errors

| Status | Code                | When                                                                                                                                                                                               | Fix                                                                                                |
| ------ | ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- |
| `400`  | `validation_failed` | No CRM is connected for this workspace                                                                                                                                                             | Connect HubSpot with [Connect an Integration](/api-reference/integration-connections/create) first |
| `401`  | `unauthorized`      | Missing or invalid API key / session                                                                                                                                                               | Send `Authorization: Bearer $API_KEY`                                                              |
| `424`  | `internal_error`    | No contact could be upserted: the provider rejected them, the property bootstrap failed, or the customer list could not be read; the message carries the underlying error (`CRM sync failed: ...`) | Fix the token or scopes in HubSpot and reconnect                                                   |
| `503`  | `internal_error`    | CRM sync is not enabled on this server (the CRM worker is not running)                                                                                                                             | Self-hosted: enable the CRM sync worker                                                            |

Errors use the standard envelope — see [Errors](/api-reference/errors).


## OpenAPI

````yaml POST /v1/crm/sync
openapi: 3.1.0
info:
  title: Recurso API
  version: 1.0.0
  description: |
    The Recurso billing engine REST API.

    Authenticate by passing your API key as a bearer token:

        Authorization: Bearer <api_key>

    Obtain an API key by registering a tenant via `POST /auth/register`.
    All authenticated endpoints live under the `/v1` prefix. Mutating
    endpoints support idempotency via the `Idempotency-Key` header.
  license:
    name: MIT
    identifier: MIT
servers:
  - url: https://billing.example.com
    description: >-
      Example deployment — substitute the base URL of your own Recurso
      deployment.
security:
  - bearerAuth: []
tags:
  - name: System
    description: Health, version, and API metadata
  - name: Auth
    description: Tenant registration
  - name: Plans
    description: Product catalog plans
  - name: Customers
    description: Customer management
  - name: Subscriptions
    description: Subscription lifecycle
  - name: Invoices
    description: Invoices, PDFs, and Indian GST e-invoicing
  - name: Coupons
    description: Discounts
  - name: Usage
    description: Metered usage events
  - name: Credit Notes
    description: Customer credits
  - name: Quotes
    description: Quote-to-invoice lifecycle
  - name: Webhooks
    description: Webhook endpoint management and event feed
  - name: Analytics
    description: Revenue analytics
  - name: Checkout
    description: Public hosted checkout for invoices
  - name: Payments
    description: Payment order creation
  - name: Inbound Webhooks
    description: Receivers for payment-gateway callbacks (Razorpay, Stripe)
  - name: Customer Portal
    description: Customer-facing portal — magic-link auth and session-scoped data
  - name: Developer
    description: API key management
  - name: Account
    description: Tenant account settings
  - name: Finance
    description: Ledger accounts, entries, reconciliation, and revenue recognition
  - name: Settings
    description: Tax (GST) and e-invoicing (IRP) configuration
  - name: Consents
    description: Consent records for RBI-compliant recurring billing
  - name: Referrals
    description: Customer referral program
  - name: Gifts
    description: Gift subscriptions
  - name: Mandates
    description: UPI Autopay mandates
  - name: Offline Payments
    description: Virtual accounts and manually recorded payments
  - name: Organizations
    description: Multi-entity organizations grouping several tenants
  - name: Accounting
    description: QuickBooks / Xero accounting integrations
  - name: Churn
    description: Churn risk scoring and alerts
  - name: Cancel Flows
    description: Configurable retention flows shown at cancellation time
  - name: Dunning
    description: Dunning analytics and multi-channel dunning campaigns
paths:
  /v1/crm/sync:
    post:
      tags:
        - Integrations
      summary: Sync this workspace's customers to its connected CRM now
      description: >
        Runs the CRM sweep for the calling workspace synchronously — the way to
        test a freshly connected HubSpot token instead of waiting for the daily
        sweep. Returns how many contacts were upserted. 400 when no CRM is
        connected; 502 carries the provider's own rejection (bad token, missing
        scopes).
      operationId: syncCRMNow
      responses:
        '200':
          description: Sweep completed.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      contacts_synced:
                        type: integer
                      contacts_remaining:
                        type: integer
                        description: >-
                          Eligible contacts not pushed this call (manual sync is
                          capped; the daily sweep finishes them).
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '424':
          description: >-
            The CRM provider rejected the sync (e.g. invalid token); the message
            carries the provider's error.
components:
  responses:
    BadRequest:
      description: The request body or parameters are invalid.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    Unauthorized:
      description: Missing or invalid credentials (API key or session cookie).
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
  schemas:
    Error:
      type: object
      required:
        - error
      properties:
        error:
          type: object
          description: Structured error detail.
          required:
            - code
            - message
          properties:
            code:
              type: string
              description: Stable machine-readable error code.
              examples:
                - validation_failed
                - unauthorized
                - forbidden
                - not_found
                - conflict
                - rate_limited
                - internal_error
                - invalid_api_key
                - key_mode_mismatch
                - over_refund
                - invoice_not_paid
                - invoice_already_paid
            message:
              type: string
              description: Human-readable explanation.
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: API key
      description: >-
        Tenant API key obtained from `POST /auth/register` or `POST
        /v1/developer/keys`.

````