---
title: "Create"
method: POST
path: "/contacts"
tags: ["contacts"]
---

# Create

`POST /contacts`

## Request

- Base URL: `https://api.instruxi.dev/api/v1/enforcer`
- URL: `https://api.instruxi.dev/api/v1/enforcer/contacts`
- Auth: one of:
  - API key in header `Authorization`
  - API key in header `X-API-Key`

Creates either a LINKED contact (pass contact_account_id — must be a discoverable account per the same rules as suggestions/lookup, or 403/404) or a CUSTOM one (omit it; requires at least one of name/email/phone/wallet — 400 bad_request otherwise). Duplicate detection is per-field: a repeat contact_account_id, custom_email, or custom_wallet_address returns 409 rather than a second row — scanning the same payment QR twice is expected to resolve to the existing contact. custom_wallet_address must be a validly-shaped address (400 invalid_wallet_address otherwise) and becomes the contact's pay-to destination.

## Request body

- InternalDeliveryHttpHandlerCreateContactRequest
  - `contact_account_id` string
  - `custom_avatar_url` string: CustomAvatarURL is this owner's private picture override for the contact — a CDN URL previously returned by an enforcer-files image upload. Set on a LINKED contact it shadows that person's own profile picture, for this owner only; on a custom contact it is the only picture available. Must be https and, when the deployment configures an allowlist, on an approved CDN host.
  - `custom_email` string
  - `custom_first_name` string
  - `custom_last_name` string
  - `custom_phone_number` string
  - `custom_wallet_address` string: CustomWalletAddress is the pay-to wallet for a custom (off-platform) contact — the QR-scan flow's payload. Previously ABSENT from this struct: the client sent it, the server silently dropped it, and wallet-only contacts landed unpayable and un-dedupable.
  - `description` string
  - `name` string

## Response `201`

Created

- InternalDeliveryHttpHandlerContactResponse
  - `data` EnforcerV3InternalDomainContactContact
    - `active` boolean
    - `alerts` EnforcerV3InternalDomainContactContactAlerts
      - `received_unseen` integer
      - `sent_unseen` integer
      - `total` integer
    - `avatar_url` string: AvatarURL is the resolved picture the client should render: the owner's CustomAvatarURL override when set, else the linked person's own profile_image_url. Computed on list/get so the PWA has ONE field to read and never has to implement the precedence itself. Never stored.
    - `blacklisted` boolean
    - `contact_account_id` string
    - `contact_type` string
    - `created_at` string
    - `created_by` string
    - `custom_avatar_url` string: CustomAvatarURL is the OWNER's private picture override for this contact (migration 000057). Meaningful on both contact types: on a linked contact it shadows the linked person's own profile_image_url, on a custom contact it is the only picture there is. nil = no override.
    - `custom_email` string
    - `custom_first_name` string
    - `custom_last_name` string
    - `custom_phone_number` string
    - `custom_wallet_address` string: CustomWalletAddress is a manually-entered pay-to wallet on a custom contact (custom contacts have no account to derive WalletAddress from). Ported from v2; only custom contacts ever set it.
    - `description` string
    - `email` string
    - `favorite_rank` integer: FavoriteRank orders the owner's favorite contacts: nil = not favorited, 1 = first favorite. A user may hold MANY favorites — the rank is a per-owner ordering, not a single slot (unique per owner among non-null ranks, see migration 000056). Favorites float to the top of every list.
    - `first_name` string
    - `id` string
    - `last_name` string
    - `my_account_id` string
    - `name` string
    - `tenant_id` string
    - `updated_at` string
    - `updated_by` string
    - `username` string: FirstName/LastName/Email are computed display fields populated by the contacts usecase on list/get: for linked contacts from the contact's account, for custom contacts mirrored from the custom_* columns. This gives the PWA one uniform name/email shape per row (matching the legacy contacts list) without a second lookup. Never stored. Username is the linked contact's account handle (@username), populated on list/get so the PWA can show @handle instead of falling back to email. Custom contacts have no account, so it stays nil. Never stored.
    - `wallet_address` string: WalletAddress is a computed field populated by the contacts usecase for linked contacts — the embedded wallet of the contact's account. Never stored in the contacts table; loaded from accounts on list/get.
  - `success` boolean

## Other responses

- `400`: Bad Request
- `401`: Unauthorized
- `403`: Forbidden
- `404`: Not Found
- `409`: Conflict

---

[API](https://skmtc.dev/instruxi/apis/enforcer-api.md) · [All operations](https://skmtc.dev/instruxi/apis/enforcer-api/llms.txt) · [OpenAPI document](https://skmtc.dev/instruxi/apis/enforcer-api/revisions/fa82bc1becc0?raw)
