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

# Who a lead was assigned to

> For most leads the agent is chosen by a routing engine that runs AFTER POST /public/leads has answered, so an ownerless lead on the create response is normal. This tells you whether an agent is coming.

Poll ~30s after creating the lead, then every `retryAfterSeconds` while `status` is `pending`, and stop at ~2 minutes. `assigned`, `unassigned` and `pool` are FINAL — stop polling. Better still, take the `lead.assigned` webhook and do not poll at all.

Returns the agent's work contacts and the status only — nothing about the customer or the enquiry.



## OpenAPI

````yaml /api-reference/openapi.json get /public/leads/{id}/assignment
openapi: 3.0.3
info:
  title: Provident — Lead Intake API
  version: 1.4.0
  description: >-
    Server-to-server API for pushing leads into the Provident CRM.


    Core endpoints:

    1. `POST /oauth2/token` — exchange client credentials for a bearer token.

    2. `POST /public/leads` — create a CRM lead.

    3. `PUT /public/leads/{id}` — add details you did not have when you created
    it (quiz answers the customer finished afterwards, a budget, an area). Send
    only what changed; phones and emails are appended, never replaced, and the
    lead's stage, status, owner and funnel are not writable.


    Optional read-only reference data, using the same token: locations,
    developers, projects and project statuses. Use them to send values that
    match ours, or to show project content on your own pages. They share the
    lead endpoint's rate limit, so cache them.


    Authentication is OAuth 2.0 Client Credentials; there is no user login. Note
    that the `/v2` prefix is part of the server URL and is mandatory.


    The intake is deliberately forgiving: name-based lookup fields that cannot
    be matched do NOT fail the request — the lead is still created and the
    unmatched values are returned in `intakeUnresolved`.


    The endpoint is not idempotent at the transport level, but intake hygiene
    runs before the lead is created: a second enquiry from the same phone/email
    within the merge window (~24h) is MERGED into the existing lead and returns
    a different, much shorter 201 body — `{ id, merged: true, matchedOn }` —
    with no contactId, intakeUnresolved, needsIntakeReview or createdAt. Always
    branch on `merged` before reading anything else. Enquiries from
    property-portal sources (Property Finder, Bayut, Dubizzle) are exempt and
    never merge. Retry only on network timeouts, 429 and 5xx.
  contact:
    name: Provident API support
servers:
  - url: https://devapi.prov.ae/v2
    description: Staging / development — build and test here
  - url: https://prodapi.prov.ae/v2
    description: Production — live leads
security: []
tags:
  - name: Authentication
    description: OAuth 2.0 client-credentials token issuance
  - name: Lead intake
    description: Public lead creation
  - name: Reference data
    description: Read-only lookups — locations, developers, projects, project statuses
  - name: Lead follow-up
    description: >-
      Reading back who a lead was assigned to, and recording the agent response
      time
  - name: Webhooks
    description: >-
      Calls Provident makes to YOUR server. Documented here for the payload
      shape; these are not endpoints you call.
paths:
  /public/leads/{id}/assignment:
    get:
      tags:
        - Lead follow-up
      summary: Who a lead was assigned to
      description: >-
        For most leads the agent is chosen by a routing engine that runs AFTER
        POST /public/leads has answered, so an ownerless lead on the create
        response is normal. This tells you whether an agent is coming.


        Poll ~30s after creating the lead, then every `retryAfterSeconds` while
        `status` is `pending`, and stop at ~2 minutes. `assigned`, `unassigned`
        and `pool` are FINAL — stop polling. Better still, take the
        `lead.assigned` webhook and do not poll at all.


        Returns the agent's work contacts and the status only — nothing about
        the customer or the enquiry.
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: Lead id returned by POST /public/leads. A merged id works too.
      responses:
        '200':
          description: Assignment state
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LeadAssignment'
              examples:
                assigned:
                  summary: An agent owns it — contact them
                  value:
                    leadId: a1b2c3d4-5566-7788-99aa-bbccddeeff00
                    status: assigned
                    agent:
                      id: 3f2a91c4-7e10-4b8d-9c33-2a5b6d7e8f90
                      slug: sarah-ahmed
                      name: Sarah Ahmed
                      email: sarah.ahmed@providentestate.com
                      phone: '+971501234567'
                      whatsappPhone: '+971501234567'
                      active: true
                    createdAt: '2026-08-24T09:14:02.881Z'
                    retryAfterSeconds: null
                pending:
                  summary: Routing has not finished — ask again, say nothing yet
                  value:
                    leadId: a1b2c3d4-5566-7788-99aa-bbccddeeff00
                    status: pending
                    agent: null
                    createdAt: '2026-08-24T09:14:02.881Z'
                    retryAfterSeconds: 30
                unassigned:
                  summary: Nobody took it — FINAL, send the generic reply
                  value:
                    leadId: a1b2c3d4-5566-7788-99aa-bbccddeeff00
                    status: unassigned
                    agent: null
                    createdAt: '2026-08-24T09:14:02.881Z'
                    retryAfterSeconds: null
        '400':
          description: The id is not a valid UUID
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          description: No such lead, or it has been deleted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
        '429':
          $ref: '#/components/responses/RateLimited'
      security:
        - bearerAuth: []
components:
  schemas:
    LeadAssignment:
      type: object
      description: >-
        Who a lead ended up with. Branch on `status`, not on whether `agent` is
        set.
      required:
        - leadId
        - status
        - agent
        - createdAt
        - retryAfterSeconds
      properties:
        leadId:
          type: string
          format: uuid
        status:
          type: string
          enum:
            - assigned
            - pending
            - unassigned
            - pool
          description: >-
            assigned = a real agent owns it, contact them. pending = routing has
            not finished, ask again after retryAfterSeconds and tell the
            customer nothing yet. unassigned = routing ran and nobody took it;
            FINAL, send the generic reply and notify no one. pool = deliberately
            ownerless in a shared pool until an agent claims it; also a generic
            reply. unassigned and pool together account for roughly 30% of leads
            — they are not errors.
        agent:
          allOf:
            - $ref: '#/components/schemas/Agent'
          nullable: true
          description: Populated only when `status` is `assigned`.
        createdAt:
          type: string
          format: date-time
          description: When the lead was created.
        retryAfterSeconds:
          type: integer
          nullable: true
          description: >-
            Seconds to wait before asking again. Non-null only when `status` is
            `pending`.
      additionalProperties: false
    ApiError:
      type: object
      description: Common error envelope used by every endpoint.
      properties:
        statusCode:
          type: integer
          example: 400
        code:
          type: string
          description: >-
            Stable machine-readable error code — branch on this, not on
            `message`.
          example: BAD_REQUEST
        message:
          type: string
          description: Human-readable description.
        errors:
          type: array
          items:
            type: string
          description: 'Present on 422 only: one entry per field that failed validation.'
        details:
          type: object
          additionalProperties: true
          description: Optional structured extras attached by the failing handler.
        path:
          type: string
          example: /v2/public/leads
        timestamp:
          type: string
          format: date-time
    Agent:
      type: object
      description: >-
        An agent, as returned anywhere in this API. Work contacts only —
        personal numbers are never published.
      required:
        - id
        - slug
        - name
        - email
        - phone
        - whatsappPhone
        - active
      properties:
        id:
          type: string
          format: uuid
          description: >-
            Portal user id. The stable, immutable identifier for an agent — key
            your records on this.
        slug:
          type: string
          nullable: true
          description: >-
            Website profile segment (/team/<slug>/), or null if not published.
            Human-readable but admin-editable; `id` is not.
          example: sarah-ahmed
        name:
          type: string
          nullable: true
          description: Display name. Safe to say to a customer.
          example: Sarah Ahmed
        email:
          type: string
          nullable: true
          description: Work email.
        phone:
          type: string
          nullable: true
          description: Work phone.
        whatsappPhone:
          type: string
          nullable: true
          description: >-
            The number to open a WhatsApp chat on. Read this field rather than
            reusing `phone` — they differ for some agents.
        active:
          type: boolean
          description: False for a deactivated account. Do not route work to them.
  responses:
    Unauthorized:
      description: Missing, invalid, revoked or expired bearer token.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiError'
          example:
            statusCode: 401
            code: OAUTH_TOKEN_EXPIRED
            message: OAuth access token expired
            path: /v2/public/projects/filters
            timestamp: '2026-07-27T06:31:04.512Z'
    RateLimited:
      description: >-
        Request quota for this client exceeded. Wait `Retry-After` seconds, then
        resume.
      headers:
        Retry-After:
          description: Seconds to wait before retrying.
          schema:
            type: integer
        X-RateLimit-Limit:
          $ref: '#/components/headers/XRateLimitLimit'
        X-RateLimit-Remaining:
          $ref: '#/components/headers/XRateLimitRemaining'
        X-RateLimit-Reset:
          $ref: '#/components/headers/XRateLimitReset'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiError'
          example:
            statusCode: 429
            code: RATE_LIMITED
            message: Too Many Requests
            path: /v2/public/leads
            timestamp: '2026-07-27T06:31:04.512Z'
  headers:
    XRateLimitLimit:
      description: Requests allowed in the current window.
      schema:
        type: integer
    XRateLimitRemaining:
      description: Requests remaining in the current window.
      schema:
        type: integer
    XRateLimitReset:
      description: Seconds until the counter resets (end of the UTC day).
      schema:
        type: integer
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: Opaque
      description: >-
        The `access_token` returned by `POST /oauth2/token`, sent as
        `Authorization: Bearer <access_token>`. It is an opaque string, not a
        JWT.

````