{
  "openapi": "3.0.3",
  "info": {
    "title": "Pipecorn Synchronous Enrichment API",
    "version": "2.0.0",
    "description": "Enrich one contact and get the result in the same response, without polling. The rest of the API is described at https://pipecorn.com/openapi.json.",
    "contact": {
      "name": "Pipecorn",
      "url": "https://pipecorn.com/contact"
    },
    "termsOfService": "https://pipecorn.com/terms-of-service",
    "x-logo": {
      "url": "https://pipecorn.com/assets/logo-pipecorn-square.svg"
    }
  },
  "servers": [
    {
      "url": "https://api.pipecorn.com/v2"
    }
  ],
  "security": [
    {
      "apiKey": []
    }
  ],
  "tags": [
    {
      "name": "Synchronous enrichment",
      "description": "Synchronous enrichment endpoints for real-time contact data enrichment"
    }
  ],
  "paths": {
    "/contacts/single_enrich": {
      "post": {
        "operationId": "enrich_contact_sync",
        "tags": [
          "Synchronous enrichment"
        ],
        "summary": "Synchronous single contact enrichment",
        "description": "Enriches a single contact with email, phone, and/or personal email data and returns results synchronously (no webhook required). Note: `personal_email` requires the user to have consented to personal email enrichment in their account settings (GDPR-compliant).",
        "requestBody": {
          "description": "Contact information for synchronous enrichment",
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SyncEnrichmentRequest"
              },
              "example": {
                "firstname": "Mathieu",
                "lastname": "Brun-Picard",
                "domain": "pipecorn.com",
                "company_name": "Pipecorn",
                "linkedin_url": "https://www.linkedin.com/in/mathieu-brun-picard/",
                "enrichment_type": [
                  "email",
                  "phone",
                  "personal_email"
                ],
                "custom": {
                  "hubspot_id": "4447901"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Enrichment completed successfully",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SyncEnrichmentResponse"
                },
                "examples": {
                  "email_and_phone_success": {
                    "summary": "Successful email and phone enrichment",
                    "value": {
                      "enrichment_id": "123e4567-e89b-12d3-a456-426614174000",
                      "first_name": "Mathieu",
                      "last_name": "Brun-Picard",
                      "linkedin_profile_url": "https://www.linkedin.com/in/mathieu-brun-picard/",
                      "status": "finished",
                      "email": "mathieu@prontohq.com",
                      "email_status": "valid",
                      "phones": [
                        "+33612345678",
                        "+33145678901"
                      ],
                      "phone_details": [
                        {
                          "number": "+33612345678",
                          "type": "mobile"
                        },
                        {
                          "number": "+33145678901",
                          "type": "fixed_line"
                        }
                      ],
                      "phone_status": "valid",
                      "custom": {
                        "hubspot_id": "4447901"
                      }
                    }
                  },
                  "email_only_success": {
                    "summary": "Successful email enrichment",
                    "value": {
                      "enrichment_id": "123e4567-e89b-12d3-a456-426614174000",
                      "first_name": "Mathieu",
                      "last_name": "Brun-Picard",
                      "linkedin_profile_url": "https://www.linkedin.com/in/mathieu-brun-picard/",
                      "status": "finished",
                      "email": "mathieu@prontohq.com",
                      "email_status": "valid"
                    }
                  },
                  "phone_only_success": {
                    "summary": "Successful phone enrichment with phone_type [\"mobile\"]",
                    "value": {
                      "enrichment_id": "123e4567-e89b-12d3-a456-426614174000",
                      "first_name": "Mathieu",
                      "last_name": "Brun-Picard",
                      "linkedin_profile_url": "https://www.linkedin.com/in/mathieu-brun-picard/",
                      "status": "finished",
                      "phones": [
                        "+33612345678"
                      ],
                      "phone_details": [
                        {
                          "number": "+33612345678",
                          "type": "mobile"
                        }
                      ],
                      "phone_status": "valid"
                    }
                  },
                  "personal_email_success": {
                    "summary": "Successful personal email enrichment",
                    "value": {
                      "enrichment_id": "123e4567-e89b-12d3-a456-426614174000",
                      "first_name": "Mathieu",
                      "last_name": "Brun-Picard",
                      "linkedin_profile_url": "https://www.linkedin.com/in/mathieu-brun-picard/",
                      "status": "finished",
                      "personal_email": "mathieu.bp@gmail.com",
                      "personal_email_status": "valid"
                    }
                  },
                  "not_found": {
                    "summary": "No email or phone found",
                    "value": {
                      "enrichment_id": "123e4567-e89b-12d3-a456-426614174000",
                      "first_name": "Mathieu",
                      "last_name": "Brun-Picard",
                      "linkedin_profile_url": "https://www.linkedin.com/in/mathieu-brun-picard/",
                      "status": "finished",
                      "email": null,
                      "email_status": "N/A",
                      "phones": [],
                      "phone_details": [],
                      "phone_status": "not_found"
                    }
                  }
                }
              }
            },
            "headers": {
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/Idempotent-Replayed"
              }
            }
          },
          "400": {
            "description": "Bad Request - Invalid enrichment_type",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "enrichment_type is required as an array: [\"email\"], [\"phone\"], [\"personal_email\"], or combinations"
                }
              }
            }
          },
          "402": {
            "description": "Not enough enrichment credits to pay for the enrichment. Checked before the waterfall runs, so no provider is called and nothing is spent. Also returned when a concurrent request drains the balance before this one is charged.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InsufficientCreditsError"
                },
                "example": {
                  "error": "Not enough enrichment credits left",
                  "code": "insufficient_credits",
                  "top_up_url": "https://app.pipecorn.com/settings/subscriptions/credits"
                }
              }
            }
          },
          "403": {
            "description": "The workspace weekly enrichment credit limit is reached. Buying credits does not lift it, so no `top_up_url` is returned.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WorkspaceLimitError"
                },
                "example": {
                  "error": "You have reached your weekly enrichment credit limit for this workspace",
                  "code": "workspace_limit_exceeded"
                }
              }
            }
          },
          "409": {
            "$ref": "#/components/responses/IdempotencyConflict"
          },
          "422": {
            "description": "Unprocessable Entity. Returned when `enrichment_type` contains an unsupported value, or when `personal_email` is requested but the user has not consented to personal email enrichment.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "unsupported_enrichment_type": {
                    "summary": "Unsupported enrichment_type value",
                    "value": {
                      "error": "enrichment_type contains unsupported values"
                    }
                  },
                  "personal_email_consent_missing": {
                    "summary": "Personal email consent missing",
                    "value": {
                      "error": "You can't use the personal email enrichment feature without consenting with our terms of use"
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Internal Server Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "An error occurred during enrichment"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ]
      }
    },
    "/contacts/check_data_availability": {
      "post": {
        "operationId": "check_data_availability",
        "tags": [
          "Synchronous enrichment"
        ],
        "summary": "Check data availability for a LinkedIn profile",
        "description": "Check whether your workspace has previously enriched a given LinkedIn profile with a professional email, personal email, and/or phone number. This endpoint is **free** and does not consume credits — it only inspects data already enriched by your workspace. Use it to decide whether to call an enrichment endpoint for a given profile.",
        "requestBody": {
          "description": "LinkedIn profile and data types to check",
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "linkedin_url",
                  "data_types"
                ],
                "properties": {
                  "linkedin_url": {
                    "type": "string",
                    "format": "uri",
                    "description": "The LinkedIn profile URL to check. Both `linkedin.com/in/{slug}` and `linkedin.com/sales/lead/{id}` style URLs are accepted, with or without a trailing slash or query string.",
                    "example": "https://www.linkedin.com/in/mathieu-brun-picard/"
                  },
                  "data_types": {
                    "type": "array",
                    "description": "The data types to check availability for. Must include at least one of: `professional_email`, `personal_email`, `phone`. Unknown values are silently ignored.",
                    "items": {
                      "type": "string",
                      "enum": [
                        "professional_email",
                        "personal_email",
                        "phone"
                      ]
                    },
                    "example": [
                      "professional_email",
                      "personal_email",
                      "phone"
                    ]
                  }
                },
                "example": {
                  "linkedin_url": "https://www.linkedin.com/in/mathieu-brun-picard/",
                  "data_types": [
                    "professional_email",
                    "personal_email",
                    "phone"
                  ]
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Availability successfully resolved. Each requested data type maps to either `Yes` (the workspace has at least one record with that data found for this profile) or `Unknown` (no record found — calling enrichment may still succeed).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "professional_email": {
                      "type": "string",
                      "enum": [
                        "Yes",
                        "Unknown"
                      ],
                      "description": "Returned when `professional_email` was requested."
                    },
                    "personal_email": {
                      "type": "string",
                      "enum": [
                        "Yes",
                        "Unknown"
                      ],
                      "description": "Returned when `personal_email` was requested."
                    },
                    "phone": {
                      "type": "string",
                      "enum": [
                        "Yes",
                        "Unknown"
                      ],
                      "description": "Returned when `phone` was requested."
                    }
                  }
                },
                "example": {
                  "professional_email": "Yes",
                  "personal_email": "Unknown",
                  "phone": "Yes"
                }
              }
            },
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimit-Limit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimit-Remaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimit-Reset"
              },
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/Idempotent-Replayed"
              }
            }
          },
          "400": {
            "description": "Bad Request — `linkedin_url` or `data_types` is missing.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "missing_linkedin_url": {
                    "summary": "linkedin_url is missing",
                    "value": {
                      "error": "linkedin_url is required"
                    }
                  },
                  "missing_data_types": {
                    "summary": "data_types is missing",
                    "value": {
                      "error": "data_types is required"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — invalid or missing API key, or the account is not allowed to use the API.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Invalid API key"
                }
              }
            }
          },
          "409": {
            "$ref": "#/components/responses/IdempotencyConflict"
          },
          "422": {
            "description": "Unprocessable Entity — `data_types` is not an array, or contains no supported value.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "not_an_array": {
                    "summary": "data_types is not an array",
                    "value": {
                      "error": "data_types must be an array"
                    }
                  },
                  "no_supported_type": {
                    "summary": "no supported data type provided",
                    "value": {
                      "error": "data_types must include at least one of: professional_email, personal_email, phone"
                    }
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ]
      }
    }
  },
  "components": {
    "securitySchemes": {
      "apiKey": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-KEY",
        "description": "Your Pipecorn API key, created at https://app.pipecorn.com/settings/apis/keys. Rate limits are counted per user, not per key."
      }
    },
    "schemas": {
      "Error": {
        "required": [
          "error"
        ],
        "type": "object",
        "properties": {
          "error": {
            "description": "Human-readable error message. A few validation errors return a list of messages, or an object with more detail, instead of a single string.",
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              {
                "type": "object"
              }
            ]
          }
        }
      },
      "WorkspaceLimitError": {
        "required": [
          "error",
          "code"
        ],
        "type": "object",
        "properties": {
          "error": {
            "type": "string",
            "example": "You have reached your weekly enrichment credit limit for this workspace"
          },
          "code": {
            "type": "string",
            "enum": [
              "workspace_limit_exceeded"
            ],
            "description": "Machine-readable error code. A 403 carrying any other code is a permission problem, not a spending limit."
          }
        }
      },
      "InsufficientCreditsError": {
        "required": [
          "error",
          "code",
          "top_up_url"
        ],
        "type": "object",
        "properties": {
          "error": {
            "type": "string",
            "description": "Human-readable message. Endpoints priced per result report the shortfall instead, e.g. \"Not enough enrichment credits. You need 30 credits but only have 5 available.\"",
            "example": "Not enough enrichment credits left"
          },
          "code": {
            "type": "string",
            "enum": [
              "insufficient_credits"
            ],
            "description": "Machine-readable error code"
          },
          "top_up_url": {
            "type": "string",
            "format": "uri",
            "description": "Absolute URL of the credits page. Buy credits there, then replay the request.",
            "example": "https://app.pipecorn.com/settings/subscriptions/credits"
          }
        }
      },
      "SyncEnrichmentRequest": {
        "type": "object",
        "required": [
          "enrichment_type"
        ],
        "properties": {
          "firstname": {
            "type": "string",
            "description": "First name of the contact"
          },
          "lastname": {
            "type": "string",
            "description": "Last name of the contact"
          },
          "company_name": {
            "type": "string",
            "description": "Company name of the contact"
          },
          "domain": {
            "type": "string",
            "description": "Company domain"
          },
          "linkedin_url": {
            "type": "string",
            "format": "uri",
            "description": "LinkedIn profile URL of the contact"
          },
          "webhook_url": {
            "type": "string",
            "format": "uri",
            "description": "Optional webhook URL to also receive enrichment results"
          },
          "enrichment_type": {
            "type": "array",
            "minItems": 1,
            "maxItems": 3,
            "items": {
              "type": "string",
              "enum": [
                "phone",
                "email",
                "personal_email"
              ]
            },
            "description": "Types of enrichment to perform. Can be any combination of [\"email\"], [\"phone\"], and/or [\"personal_email\"]. Note: \"personal_email\" requires the user to have consented to personal email enrichment in their account settings. Requests including \"personal_email\" or \"phone\" require `linkedin_url`."
          },
          "phone_country_codes": {
            "type": "array",
            "items": {
              "type": "string",
              "description": "ISO 3166-1 alpha-2 country code (e.g. \"US\", \"FR\", \"GB\")"
            },
            "description": "Optional. Restrict phone enrichment to phone numbers from these countries. When omitted, the user's account-level default phone country codes are applied."
          },
          "phone_type": {
            "type": "array",
            "maxItems": 1,
            "items": {
              "type": "string",
              "enum": [
                "all",
                "mobile"
              ]
            },
            "default": [
              "all"
            ],
            "description": "Optional. Filters the phone numbers returned by phone enrichment. `[\"all\"]` (default) returns every number found; `[\"mobile\"]` returns only mobile numbers (`type` of `mobile` or `fixed_or_mobile`) and drops landlines, VoIP, toll-free and other non-mobile numbers. With `[\"mobile\"]` the waterfall keeps querying providers until one returns a mobile number. Any other value is rejected with `422`. Ignored unless `enrichment_type` includes `phone`."
          },
          "custom": {
            "type": "object",
            "description": "Custom fields to include with the enrichment response",
            "additionalProperties": true
          }
        }
      },
      "SyncEnrichmentResponse": {
        "type": "object",
        "properties": {
          "enrichment_id": {
            "type": "string",
            "format": "uuid",
            "description": "The ID of the enrichment request"
          },
          "first_name": {
            "type": "string",
            "description": "The contact's first name"
          },
          "last_name": {
            "type": "string",
            "description": "The contact's last name"
          },
          "linkedin_profile_url": {
            "type": "string",
            "format": "uri",
            "description": "The contact's LinkedIn profile URL"
          },
          "status": {
            "type": "string",
            "enum": [
              "finished",
              "failed"
            ],
            "description": "`finished` once the enrichment waterfall has run, even when nothing was found (check the per-type status fields), or `failed` when it did not complete"
          },
          "email": {
            "type": "string",
            "format": "email",
            "description": "The enriched email address (present if enrichment_type includes email)"
          },
          "email_status": {
            "type": "string",
            "enum": [
              "valid",
              "invalid",
              "N/A"
            ],
            "description": "The validation status of the email (present if enrichment_type includes email)"
          },
          "personal_email": {
            "type": "string",
            "format": "email",
            "description": "The enriched personal email address (present if enrichment_type includes personal_email)"
          },
          "personal_email_status": {
            "type": "string",
            "enum": [
              "valid",
              "invalid",
              "N/A"
            ],
            "description": "The validation status of the personal email (present if enrichment_type includes personal_email)"
          },
          "phones": {
            "type": "array",
            "description": "Array of phone numbers found, after the `phone_type` filter is applied (present if enrichment_type includes phone)",
            "items": {
              "type": "string",
              "description": "Phone number in E.164 format"
            }
          },
          "phone_details": {
            "type": "array",
            "description": "Phone numbers found, each with its line type. Same numbers and order as `phones`, after the `phone_type` filter is applied (empty array when none found).",
            "items": {
              "type": "object",
              "properties": {
                "number": {
                  "type": "string",
                  "description": "Phone number in E.164 format"
                },
                "type": {
                  "type": "string",
                  "enum": [
                    "mobile",
                    "fixed_or_mobile",
                    "fixed_line",
                    "voip",
                    "toll_free",
                    "premium_rate",
                    "shared_cost",
                    "personal_number",
                    "pager",
                    "uan",
                    "voicemail",
                    "unknown"
                  ],
                  "description": "Line type derived from the number's numbering plan. `fixed_or_mobile` is used in countries where mobile and landline numbers share the same ranges (e.g. the US and Canada); it counts as mobile for the `phone_type` filter and for billing. `unknown` means the number could not be classified."
                }
              }
            }
          },
          "phone_status": {
            "type": "string",
            "enum": [
              "valid",
              "not_found"
            ],
            "description": "The status of phone enrichment (present if enrichment_type includes phone)"
          },
          "custom": {
            "type": "object",
            "description": "Custom fields passed in the request",
            "additionalProperties": true
          }
        }
      }
    },
    "headers": {
      "RateLimit-Limit": {
        "description": "Requests allowed per minute on your plan for this group of endpoints. Absent for unlimited accounts and on endpoints exempt from the plan limit.",
        "schema": {
          "type": "integer",
          "example": 100
        }
      },
      "RateLimit-Remaining": {
        "description": "Requests left in the current one-minute window.",
        "schema": {
          "type": "integer",
          "example": 97
        }
      },
      "RateLimit-Reset": {
        "description": "Seconds until the current window resets and `RateLimit-Remaining` is back to `RateLimit-Limit`.",
        "schema": {
          "type": "integer",
          "example": 42
        }
      },
      "Retry-After": {
        "description": "Seconds to wait before retrying. Sent when the plan limit or a request still in progress caused the error.",
        "schema": {
          "type": "integer",
          "example": 42
        }
      },
      "Idempotent-Replayed": {
        "description": "`true` when this response is a replay of an earlier request sent with the same `Idempotency-Key`. Nothing was run or charged again.",
        "schema": {
          "type": "string",
          "enum": [
            "true"
          ]
        }
      }
    },
    "parameters": {
      "IdempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": false,
        "description": "A unique value you generate per operation, such as a UUID. Retrying with the same key and the same body returns the stored response instead of running the request again, so a retry after a timeout never charges twice. Successful responses are kept for 24 hours; a failed request is not stored and can be retried with the same key.",
        "schema": {
          "type": "string",
          "maxLength": 255,
          "example": "4f1c2e9a-8a7d-4c1b-9f63-2d6e0b5a7c11"
        }
      }
    },
    "responses": {
      "IdempotencyConflict": {
        "description": "A request with this `Idempotency-Key` is still being processed. Wait for the `Retry-After` delay and retry with the same key.",
        "headers": {
          "Retry-After": {
            "$ref": "#/components/headers/Retry-After"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "properties": {
                "error": {
                  "type": "string"
                },
                "code": {
                  "type": "string",
                  "enum": [
                    "idempotency_key_in_use"
                  ]
                }
              }
            },
            "example": {
              "error": "A request with this Idempotency-Key is still being processed",
              "code": "idempotency_key_in_use"
            }
          }
        }
      }
    }
  }
}
