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

# Create or upsert an entity

> Ingest an entity customer (company, trust, partnership, SMSF). The
record is created with `kyb_status = NOT_STARTED` and
`added_via = INTEGRATION`. **No KYB is triggered and no credits are
charged** — your back-office team starts verification (and chooses
the KYB method) from inside Instant Compliance.

### Identifiers

* `AUSTRALIAN` entities (the default) accept `abn` (11 digits) and
  `acn` (9 digits). Spaces are tolerated and stripped.
* `INTERNATIONAL` entities require `countryOfFormation`
  (ISO 3166-1 alpha-3) and accept `registrationNumber`; `abn`/`acn`
  are rejected.

### Contact person

KYB needs an individual who acts for the entity. Pass `contact`
with either `customerId` (an existing individual's UUID or
`external_id`) or an inline `fullName` + `email` (which reuses an
exact name+email match or creates a contact-role individual).
`contact.kycRequired` (default `true`) records the *intent* to
verify the contact alongside the entity's KYB — nothing is sent,
armed, or billed until your team starts the KYB in-app.

### Designated services

`designatedServices` behaves exactly as on `POST /customers`: the
list replaces the entity's set, derives the designated flag,
recomputes risk, and an explicit empty array creates the record
with `kyb_status = NOT_REQUIRED`.

### Idempotency

* If `externalId` is supplied and already exists in your
  organisation, the existing record is **updated** and the
  response is **200 OK**.
* Otherwise a match on `abn`, then `acn`, then legal name + entity
  type (case-insensitive) updates that record — unless it carries a
  *different* `external_id`, which is a **409 conflict**.
* Otherwise a new record is created and returned with **201 Created**.

`type` and `origin` are immutable — an upsert that disagrees with
the stored values is a **409 conflict**. Once the entity's KYB is
**VERIFIED**, identity fields (`name`, `abn`, `acn`,
`countryOfFormation`) freeze — re-sending unchanged values is fine,
changing them returns **422 validation_failed**.

Include an `Idempotency-Key` header to make network retries safe
for 24 hours.


Ingest an entity customer — a company, trust, partnership, or SMSF.
The record lands in your organisation tagged `INTEGRATION` with
`kyb_status = NOT_STARTED`. **No KYB is triggered. No credits are
charged.** Your back-office team chooses the KYB method and starts
verification in-app when ready.

## Individuals vs entities

Entities verify via **KYB** (business verification + beneficial-owner
resolution) instead of KYC, so they live on their own resource. Use
[`POST /customers`](/api-reference/customers/create) for individuals
and sole traders.

## Identifiers

* `AUSTRALIAN` entities (the default) take `abn` (11 digits) and `acn`
  (9 digits). Spaces are tolerated and stripped.
* `INTERNATIONAL` entities require `countryOfFormation` (ISO 3166-1
  alpha-3) and accept `registrationNumber`; `abn`/`acn` are rejected.

## Contact person

KYB needs an individual who acts for the entity — they receive the
verification link when your team starts KYB. Pass `contact` with either
`customerId` (an existing individual's UUID or `external_id`) or an
inline `fullName` + `email`. Inline contacts reuse an exact name+email
match or create a contact-role individual; nothing is emailed or billed
at ingest time.

## Idempotent upsert behaviour

| Input                 | Match found?                                                                    | Result                 | Status         |
| --------------------- | ------------------------------------------------------------------------------- | ---------------------- | -------------- |
| `externalId` supplied | Yes (same org)                                                                  | Update existing record | `200 OK`       |
| —                     | `abn`, then `acn`, then legal name + type matches (no conflicting `externalId`) | Adopt + update         | `200 OK`       |
| —                     | Match carries a *different* `externalId`                                        | Reject                 | `409 conflict` |
| —                     | Stored `type`/`origin` disagree with the payload                                | Reject                 | `409 conflict` |
| Nothing matches       | —                                                                               | Create new             | `201 Created`  |

Once the entity's KYB is **VERIFIED**, identity fields (`name`, `abn`,
`acn`, `countryOfFormation`) freeze — re-sending unchanged values is
fine, changing them returns `422 validation_failed`.

## Retry safety

Pass `Idempotency-Key` (a UUID or any string ≤ 255 chars). Repeated
calls with the same key within 24 hours replay the original response.
See [Idempotency](/concepts/idempotency).


## OpenAPI

````yaml POST /entities
openapi: 3.1.0
info:
  title: Instant Compliance API
  version: 1.0.0
  summary: External integration API for Instant Compliance.
  description: |
    The Instant Compliance v1 API lets your systems push customers into
    Instant Compliance and pull their verification / AML status back out.
    Designed for server-to-server use (CRMs, Zapier, internal back-office
    tools).

    **Base URL:** `https://app.instantcompliance.ai/api/v1`

    **Authentication:** Bearer API key — `Authorization: Bearer ic_live_…`
    Issue keys from **Settings → Developers** in your Instant Compliance
    organisation.

    **Scope:** individual customers (`INDIVIDUAL` and `SOLE_TRADER`, KYC)
    live on `/customers`; entity customers (companies, trusts,
    partnerships, SMSFs — KYB) live on `/entities`.
  contact:
    name: Instant Compliance Support
    email: support@instantcompliance.ai
    url: https://instantcompliance.ai
  license:
    name: Proprietary
servers:
  - url: https://app.instantcompliance.ai/api/v1
    description: Production
security:
  - bearerAuth: []
tags:
  - name: Customers
    description: |
      Create, read, and update individual customer records ingested through
      the external API.
  - name: Entities
    description: |
      Create, read, and update entity customer records (companies, trusts,
      partnerships, SMSFs). Entities verify via KYB instead of KYC; the API
      ingests the record and reports status — verification is started by
      your back-office team in-app.
  - name: Designated services
    description: |
      Read-only catalog of the designated-service codes accepted when
      creating customers.
  - name: Groups
    description: |
      Create group workspaces (multi-party deals/matters — e.g. one per
      property for real-estate integrators) and discover the group
      templates available to your organisation. Requires the Groups
      feature to be enabled for your organisation.
paths:
  /entities:
    post:
      tags:
        - Entities
      summary: Create or upsert an entity
      description: |
        Ingest an entity customer (company, trust, partnership, SMSF). The
        record is created with `kyb_status = NOT_STARTED` and
        `added_via = INTEGRATION`. **No KYB is triggered and no credits are
        charged** — your back-office team starts verification (and chooses
        the KYB method) from inside Instant Compliance.

        ### Identifiers

        * `AUSTRALIAN` entities (the default) accept `abn` (11 digits) and
          `acn` (9 digits). Spaces are tolerated and stripped.
        * `INTERNATIONAL` entities require `countryOfFormation`
          (ISO 3166-1 alpha-3) and accept `registrationNumber`; `abn`/`acn`
          are rejected.

        ### Contact person

        KYB needs an individual who acts for the entity. Pass `contact`
        with either `customerId` (an existing individual's UUID or
        `external_id`) or an inline `fullName` + `email` (which reuses an
        exact name+email match or creates a contact-role individual).
        `contact.kycRequired` (default `true`) records the *intent* to
        verify the contact alongside the entity's KYB — nothing is sent,
        armed, or billed until your team starts the KYB in-app.

        ### Designated services

        `designatedServices` behaves exactly as on `POST /customers`: the
        list replaces the entity's set, derives the designated flag,
        recomputes risk, and an explicit empty array creates the record
        with `kyb_status = NOT_REQUIRED`.

        ### Idempotency

        * If `externalId` is supplied and already exists in your
          organisation, the existing record is **updated** and the
          response is **200 OK**.
        * Otherwise a match on `abn`, then `acn`, then legal name + entity
          type (case-insensitive) updates that record — unless it carries a
          *different* `external_id`, which is a **409 conflict**.
        * Otherwise a new record is created and returned with **201 Created**.

        `type` and `origin` are immutable — an upsert that disagrees with
        the stored values is a **409 conflict**. Once the entity's KYB is
        **VERIFIED**, identity fields (`name`, `abn`, `acn`,
        `countryOfFormation`) freeze — re-sending unchanged values is fine,
        changing them returns **422 validation_failed**.

        Include an `Idempotency-Key` header to make network retries safe
        for 24 hours.
      operationId: createEntity
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateEntityRequest'
            examples:
              minimal:
                summary: Minimum required fields
                value:
                  externalId: crm-ent-311
                  type: COMPANY
                  name: Acme Holdings Pty Ltd
              australianCompany:
                summary: Australian company with identifiers + contact
                value:
                  externalId: crm-ent-311
                  type: COMPANY
                  name: Acme Holdings Pty Ltd
                  abn: '12345678901'
                  acn: '123456789'
                  contact:
                    fullName: Jane Doe
                    email: jane@example.com
                  designatedServices:
                    - PRO-FORMATION
              internationalCompany:
                summary: International entity
                value:
                  externalId: crm-ent-312
                  type: COMPANY
                  name: Acme Global Ltd
                  origin: INTERNATIONAL
                  countryOfFormation: GBR
                  registrationNumber: '09876543'
              smsf:
                summary: SMSF referencing an existing individual as contact
                value:
                  externalId: crm-ent-313
                  type: SMSF
                  name: Greenfield Superannuation Fund
                  abn: '98765432109'
                  contact:
                    customerId: crm-7741
      responses:
        '200':
          description: Existing entity updated (idempotent upsert).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Entity'
        '201':
          description: New entity created.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Entity'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/ForbiddenScope'
        '409':
          description: |
            Identifier (external_id / ABN / ACN / name) belongs to a
            different record, or the record exists with a different
            immutable type/origin.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '422':
          $ref: '#/components/responses/ValidationFailed'
        '429':
          $ref: '#/components/responses/RateLimited'
      x-codeSamples:
        - lang: curl
          source: |
            curl -X POST https://app.instantcompliance.ai/api/v1/entities \
              -H "Authorization: Bearer ic_live_..." \
              -H "Content-Type: application/json" \
              -H "Idempotency-Key: $(uuidgen)" \
              -d '{
                "externalId": "crm-ent-311",
                "type": "COMPANY",
                "name": "Acme Holdings Pty Ltd",
                "abn": "12345678901",
                "contact": { "fullName": "Jane Doe", "email": "jane@example.com" }
              }'
        - lang: javascript
          source: |
            await fetch('https://app.instantcompliance.ai/api/v1/entities', {
              method: 'POST',
              headers: {
                'Authorization': `Bearer ${process.env.IC_API_KEY}`,
                'Content-Type': 'application/json',
                'Idempotency-Key': crypto.randomUUID()
              },
              body: JSON.stringify({
                externalId: 'crm-ent-311',
                type: 'COMPANY',
                name: 'Acme Holdings Pty Ltd',
                abn: '12345678901',
                contact: { fullName: 'Jane Doe', email: 'jane@example.com' }
              })
            });
        - lang: python
          source: |
            import os, uuid, requests
            requests.post(
              'https://app.instantcompliance.ai/api/v1/entities',
              headers={
                'Authorization': f"Bearer {os.environ['IC_API_KEY']}",
                'Idempotency-Key': str(uuid.uuid4())
              },
              json={
                'externalId': 'crm-ent-311',
                'type': 'COMPANY',
                'name': 'Acme Holdings Pty Ltd',
                'abn': '12345678901',
                'contact': {'fullName': 'Jane Doe', 'email': 'jane@example.com'}
              }
            )
components:
  parameters:
    IdempotencyKey:
      in: header
      name: Idempotency-Key
      required: false
      schema:
        type: string
        maxLength: 255
      description: |
        Caller-supplied unique key for safe retries. Repeat the same key
        within 24 hours and we replay the original response instead of
        repeating the side effect. Reusing the key with a *different*
        request body returns **409 idempotency_conflict**.
  schemas:
    CreateEntityRequest:
      type: object
      required:
        - type
        - name
      additionalProperties: false
      properties:
        externalId:
          type: string
          maxLength: 255
          description: >-
            Your CRM identifier. Strongly recommended for idempotent upsert +
            round-tripping.
        type:
          $ref: '#/components/schemas/EntityType'
        name:
          type: string
          minLength: 1
          maxLength: 255
          description: The entity's legal name.
        origin:
          $ref: '#/components/schemas/EntityOrigin'
        abn:
          type: string
          nullable: true
          description: >-
            Australian Business Number (11 digits; spaces tolerated). AUSTRALIAN
            entities only.
        acn:
          type: string
          nullable: true
          description: >-
            Australian Company Number (9 digits; spaces tolerated). AUSTRALIAN
            entities only.
        address:
          type: string
          maxLength: 500
          nullable: true
        countryOfFormation:
          type: string
          nullable: true
          description: >-
            ISO 3166-1 alpha-3. Required for INTERNATIONAL entities; rejected
            for AUSTRALIAN.
        registrationNumber:
          type: string
          maxLength: 64
          nullable: true
          description: >-
            Company/registry number in the country of formation. INTERNATIONAL
            entities only.
        contact:
          $ref: '#/components/schemas/EntityContactInput'
        risk:
          $ref: '#/components/schemas/RiskAnswers'
        designatedServices:
          $ref: '#/components/schemas/DesignatedServices'
    Entity:
      type: object
      required:
        - id
        - type
        - name
        - origin
        - kyb_status
        - aml
        - added_via
        - created_at
        - updated_at
      properties:
        id:
          type: string
          format: uuid
          description: Instant Compliance customer UUID (entities are customers too).
        external_id:
          type: string
          nullable: true
        type:
          $ref: '#/components/schemas/EntityType'
        name:
          type: string
          description: The entity's legal name.
        origin:
          $ref: '#/components/schemas/EntityOrigin'
        abn:
          type: string
          nullable: true
        acn:
          type: string
          nullable: true
        country_of_formation:
          type: string
          nullable: true
          description: |
            ISO 3166-1 alpha-3. `AUS` for Australian entities; the country
            of formation for international entities.
        registration_number:
          type: string
          nullable: true
          description: Registry number recorded for international entities.
        kyb_status:
          $ref: '#/components/schemas/KycStatus'
        kyb_started_at:
          type: string
          format: date-time
          nullable: true
        kyb_completed_at:
          type: string
          format: date-time
          nullable: true
        contact:
          $ref: '#/components/schemas/EntityContact'
        aml:
          $ref: '#/components/schemas/AmlBlock'
        added_via:
          $ref: '#/components/schemas/AddedVia'
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
      example:
        id: 7c9e6679-7425-40de-944b-e07fc1f90ae7
        external_id: crm-ent-311
        type: COMPANY
        name: Acme Holdings Pty Ltd
        origin: AUSTRALIAN
        abn: '12345678901'
        acn: '123456789'
        country_of_formation: AUS
        registration_number: null
        kyb_status: IN_PROGRESS
        kyb_started_at: '2026-06-23T01:00:00Z'
        kyb_completed_at: null
        contact:
          id: 550e8400-e29b-41d4-a716-446655440000
          external_id: crm-7741
          full_name: Jane Doe
          email: jane@example.com
          kyc_status: PENDING
          kyc_required: true
        aml:
          status: CLEAR
          screened_at: '2026-06-23T01:12:00Z'
          last_reviewed_at: null
          flags:
            pep: false
            sanctions: false
            adverse_media: false
            terrorism: false
        added_via: INTEGRATION
        created_at: '2026-06-22T22:14:00Z'
        updated_at: '2026-06-23T01:12:05Z'
    Error:
      type: object
      required:
        - error
      properties:
        error:
          type: object
          required:
            - code
            - message
          properties:
            code:
              type: string
              enum:
                - unauthorized
                - forbidden_scope
                - not_found
                - validation_failed
                - conflict
                - insufficient_credits
                - rate_limited
                - idempotency_conflict
                - feature_disabled
                - internal
            message:
              type: string
            details:
              type: object
              additionalProperties: true
    EntityType:
      type: string
      enum:
        - COMPANY
        - TRUST
        - PARTNERSHIP
        - SMSF
        - OTHER
      description: |
        Entity customer types accepted by `/entities`. `SMSF` is treated
        as a type of trust and `OTHER` as a company for verification
        purposes, but the type you post round-trips back unchanged.
        Immutable after creation.
    EntityOrigin:
      type: string
      enum:
        - AUSTRALIAN
        - INTERNATIONAL
      description: |
        Where the entity is formed. `AUSTRALIAN` (the default) uses
        ABN/ACN identifiers; `INTERNATIONAL` uses `countryOfFormation` +
        `registrationNumber`. Immutable after creation.
    EntityContactInput:
      type: object
      additionalProperties: false
      description: |
        The entity's primary contact person — the individual who acts for
        the entity and receives the KYB portal link when your team starts
        verification. Provide **either** `customerId` (an existing
        individual) **or** `fullName` + `email` (inline create/reuse) —
        not both.
      properties:
        customerId:
          type: string
          maxLength: 255
          description: |
            An existing individual customer — Instant Compliance's UUID or
            your own `external_id`. Must reference an individual, not an
            entity.
        fullName:
          type: string
          minLength: 1
          maxLength: 255
          description: >-
            Inline contact — reuses an exact name+email match or creates a
            contact-role individual.
        email:
          type: string
          format: email
          maxLength: 255
        kycRequired:
          type: boolean
          default: true
          description: |
            Intent to verify the contact's own identity alongside the
            entity's KYB. Recorded only — nothing is sent, armed, or
            billed until your team starts the KYB in-app.
    RiskAnswers:
      type: object
      description: |
        Optional risk pre-answers. Your back-office team will complete the
        full risk assessment in-app before triggering KYC; these values
        seed it.
      additionalProperties: false
      properties:
        isNewCustomer:
          type: boolean
          description: New relationship vs an existing client of yours.
        isDesignatedService:
          type: boolean
          description: |
            Whether you provide a designated service to this customer.
            Prefer `designatedServices` (the specific service codes) — when
            that list is present this flag is derived from it, and sending a
            contradictory value is rejected with `422`.
        handlesCashOrCrypto:
          type: boolean
          description: Whether the engagement handles physical cash or cryptocurrency.
    DesignatedServices:
      type: array
      maxItems: 64
      items:
        type: string
        maxLength: 64
      description: >
        The specific designated service(s) provided to the customer, as

        stable catalog codes. An empty array is an explicit "no designated

        services". Unknown or non-designated codes are rejected with

        `422 validation_failed`.


        The catalog is also available programmatically via

        `GET /designated-services`.


        **Professional services**


        | Code | Service |

        |------|---------|

        | `PRO-FORMATION` | Company or trust formation |

        | `PRO-PURCHASE-SALE` | Company or trust purchase/sale |

        | `PRO-RESTRUCTURE` | Business restructuring services |

        | `PRO-REAL-ESTATE` | Real estate professional services (conveyancing /
        legal) |

        | `PRO-CLIENT-MONEY` | Client money/assets management (incl. securities
        & virtual assets) |

        | `PRO-FINANCING` | Business financing arrangement services |

        | `PRO-SHELF` | Shelf company sales |

        | `PRO-NOMINEE` | Nominee director or shareholder services |

        | `PRO-REGISTERED-OFFICE` | Registered office address services |


        **Real estate**


        | Code | Service |

        |------|---------|

        | `RE-BROKER` | Real estate agency (brokering sales) |

        | `RE-DEVELOPER` | Property development sales |


        **Financial services**


        | Code | Service |

        |------|---------|

        | `FIN-BANKING` | Banking and deposit services |

        | `FIN-REMITTANCE` | Money transfer and remittance services |

        | `FIN-CRYPTO` | Cryptocurrency exchange services |

        | `FIN-FX` | Foreign currency exchange |

        | `FIN-LOANS` | Loans and financing services |

        | `FIN-INVESTMENT` | Investment and securities services |

        | `FIN-ADVISORY` | Financial advisory services (arranging designated
        services) |

        | `FIN-INSURANCE` | Insurance services (life insurance, sinking funds) |

        | `FIN-SUPER` | Superannuation fund management |

        | `FIN-PENSION` | Pension, annuity, or retirement account services |

        | `FIN-STORED-VALUE` | High-value stored value cards |

        | `FIN-CUSTODIAL` | Custodial and depository services |

        | `FIN-PAYROLL` | Payroll services for other businesses |


        **Other designated services**


        | Code | Service |

        |------|---------|

        | `OTH-BULLION` | Bullion trading (precious metals) |

        | `OTH-GAMBLING` | Gambling services |

        | `OTH-HIGH-VALUE-GOODS` | High-value goods dealing |
      example:
        - PRO-FORMATION
        - PRO-CLIENT-MONEY
    KycStatus:
      type: string
      enum:
        - NOT_STARTED
        - PENDING
        - IN_PROGRESS
        - VERIFIED
        - FAILED
        - NOT_REQUIRED
        - AWAITING_RESUBMISSION
    EntityContact:
      type: object
      nullable: true
      required:
        - id
        - full_name
        - kyc_status
        - kyc_required
      description: The entity's current contact person, or `null` when none is set.
      properties:
        id:
          type: string
          format: uuid
        external_id:
          type: string
          nullable: true
        full_name:
          type: string
        email:
          type: string
          format: email
          nullable: true
        kyc_status:
          $ref: '#/components/schemas/KycStatus'
        kyc_required:
          type: boolean
          description: >-
            Whether the contact's own KYC is intended to run with the entity's
            KYB.
    AmlBlock:
      type: object
      required:
        - status
        - flags
      properties:
        status:
          $ref: '#/components/schemas/AmlStatus'
        screened_at:
          type: string
          format: date-time
          nullable: true
        last_reviewed_at:
          type: string
          format: date-time
          nullable: true
        flags:
          type: object
          required:
            - pep
            - sanctions
            - adverse_media
            - terrorism
          properties:
            pep:
              type: boolean
            sanctions:
              type: boolean
            adverse_media:
              type: boolean
            terrorism:
              type: boolean
    AddedVia:
      type: string
      description: How the record entered Instant Compliance.
      enum:
        - ADMIN_MANUAL
        - AI_EXTRACTED
        - CONTACT_PORTAL
        - BULK_IMPORT
        - INTEGRATION
        - SYSTEM
    AmlStatus:
      type: string
      enum:
        - NOT_SCREENED
        - IN_PROGRESS
        - CLEAR
        - NEEDS_REVIEW
        - RESOLVED
  responses:
    Unauthorized:
      description: Missing, invalid, expired, or revoked API key.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              code: unauthorized
              message: Invalid or revoked API key.
    ForbiddenScope:
      description: API key lacks the required scope.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              code: forbidden_scope
              message: This API key does not have the required scope. customers:write
              details:
                required_scope: customers:write
    ValidationFailed:
      description: Request payload failed schema validation.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              code: validation_failed
              message: Invalid customer payload.
              details:
                issues:
                  email:
                    - Must be a valid email address.
    RateLimited:
      description: Burst or daily rate limit exceeded for this API key.
      headers:
        Retry-After:
          schema:
            type: integer
          description: Seconds to wait before retrying.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              code: rate_limited
              message: Rate limit exceeded. Slow down and retry shortly.
              details:
                limit: 60
                window_seconds: 60
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: API Key
      description: |
        Bearer API key issued from **Settings → Developers** in your
        Instant Compliance organisation. Format: `ic_live_…`.

````