{
  "openapi": "3.1.0",
  "info": {
    "title": "Lancer Public API",
    "summary": "Read matched Upwork jobs and act on them: bid, reject, or attach files.",
    "version": "2026-09-10",
    "description": "Lancer watches Upwork for jobs that match a campaign’s saved filters and turns each match into a **lead**. This API is the bidding loop over those leads: list them, submit a proposal, reject one, or attach files to a proposal.\n\nIt is intentionally small — four operations, all scoped to leads inside one campaign.\n\n## Authentication\n\nEvery operation requires an organization-scoped API key sent as a bearer token:\n\n```\nAuthorization: Bearer lk_9f2c4e6a8b1d3f5079e2c4a6b8d0f1a3c5e7092b4d6f8a0c2e4b6d8f0a1c3e5f\n```\n\nKeys start with `lk_` followed by 64 hex characters and are created in the Lancer web app under Settings → Integrations → API keys, or through the `api_key_create` tool on the Lancer MCP server. The full key is shown once, at creation. There is no query-parameter or custom-header alternative.\n\nCreating keys and calling this API both require an active paid subscription. If the subscription lapses or billing collection is paused, previously valid keys start returning `401`.\n\nDo not confuse this key with the Lancer **MCP** token (`lmc_…`), which is personal rather than organization-scoped and authenticates `https://api.lancer.app/mcp` instead.\n\n## Conventions\n\n- **Timestamps** inside lead objects are UNIX **milliseconds**. Webhook envelope timestamps are UNIX seconds.\n- **Pagination** is `offset`/`limit` on `listCampaignLeads`. `limit` defaults to 100 and is capped at 100. Keep paging while `hasMore` is true.\n- **Money** is USD. Boost amounts in `biddingConfig` are denominated in Upwork *connects* (1 connect = USD 0.15), not dollars.\n- **Screening questions must match verbatim.** Every `questionAnswerPairs[].question` has to be byte-identical to a string in the lead’s `questions` array. This is the most common integration failure, because language models paraphrase. On mismatch the `400` body lists the accepted strings in `errors[].options` so the caller can self-correct and retry.\n- **Dry runs.** `submitBid` accepts `\"dryRun\": true` to validate the request and fire a simulated `bid_complete` webhook without spending connects, contacting Upwork, or changing the lead. A passing dry run proves the schema and the verbatim questions are right; it does not check your proposal quota or whether the lead was already bid on.\n\n## Error shapes\n\nThree bodies are possible: a simple `{ statusCode, message, error }`, a validation failure `{ message, errors[] }` with a machine-readable `code` per entry, and a quota failure `{ message, code, … }`. Each operation lists which it can return.\n\n## Rate limits\n\nThere is no published request rate limit. The practical ceilings are: 100 leads per page, 3 MB JSON request bodies, and 5 attachments of 25 MB per lead. Sustained abusive traffic may get an explicit limit in future; no `RateLimit-*` or `Retry-After` headers are emitted today.\n\n## Side effect worth knowing\n\n`listCampaignLeads` is not a pure read. Every job on the returned page is materialized into a persisted lead so it can be acted on immediately, which means paging deeply through a campaign creates lead records.",
    "termsOfService": "https://lancer.app/terms-of-service",
    "contact": {
      "name": "Lancer support",
      "email": "support@lancer.app",
      "url": "https://docs.lancer.app/docs/external-mode/api"
    },
    "license": {
      "name": "Proprietary",
      "url": "https://lancer.app/terms-of-service"
    }
  },
  "externalDocs": {
    "description": "Full Public API and webhook documentation",
    "url": "https://docs.lancer.app/docs/external-mode"
  },
  "servers": [
    {
      "url": "https://api.lancer.app",
      "description": "Production"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "tags": [
    {
      "name": "Leads",
      "description": "Jobs matched to a campaign, and the actions you can take on them."
    }
  ],
  "paths": {
    "/v1/campaigns/{campaignId}/leads": {
      "get": {
        "operationId": "listCampaignLeads",
        "summary": "List leads for a campaign",
        "description": "Returns Upwork jobs matching the campaign’s saved filters, newest first, each as a lead with a stable id and ready-to-call bid and reject URLs. Works on new campaigns with no webhook history. Creates lead records as a side effect.",
        "tags": [
          "Leads"
        ],
        "x-openai-isConsequential": false,
        "parameters": [
          {
            "name": "campaignId",
            "in": "path",
            "required": true,
            "description": "The campaign whose filters select the jobs.",
            "schema": {
              "type": "string",
              "examples": [
                "cmp_123"
              ]
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Number of leads to skip. Use with the previous response’s total and hasMore to page.",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Page size. Values above 100 are clamped to 100.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of leads.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "title": "LeadPage",
                  "additionalProperties": false,
                  "required": [
                    "data",
                    "total",
                    "offset",
                    "limit",
                    "hasMore"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Lead"
                      }
                    },
                    "total": {
                      "type": "integer",
                      "description": "Matching leads across all pages.",
                      "examples": [
                        342
                      ]
                    },
                    "offset": {
                      "type": "integer",
                      "description": "Echo of the offset used.",
                      "examples": [
                        0
                      ]
                    },
                    "limit": {
                      "type": "integer",
                      "description": "Echo of the limit used.",
                      "examples": [
                        10
                      ]
                    },
                    "hasMore": {
                      "type": "boolean",
                      "description": "True while offset + data.length < total. Stop paging when false."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key, or the organization has no active paid subscription. Do not retry without a different key or a fixed billing state.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "The campaign or lead does not exist, or belongs to a different organization. Do not retry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/campaigns/{campaignId}/leads/{leadId}/bid": {
      "post": {
        "operationId": "submitBid",
        "summary": "Submit a proposal for a lead",
        "description": "Validates and queues an Upwork proposal for this lead using your cover letter, screening answers and the campaign’s bidding config. A success response means queued, not submitted: subscribe to the bid_complete webhook to learn when the proposal is really on Upwork.",
        "tags": [
          "Leads"
        ],
        "x-openai-isConsequential": true,
        "parameters": [
          {
            "name": "campaignId",
            "in": "path",
            "required": true,
            "description": "The campaign the lead belongs to.",
            "schema": {
              "type": "string",
              "examples": [
                "cmp_123"
              ]
            }
          },
          {
            "name": "leadId",
            "in": "path",
            "required": true,
            "description": "The lead to bid on, from listCampaignLeads or a job.new webhook.",
            "schema": {
              "type": "string",
              "examples": [
                "lead_abc123"
              ]
            }
          }
        ],
        "requestBody": {
          "required": true,
          "description": "The proposal. Only coverLetter and questionAnswerPairs are required.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SubmitBidRequest"
              },
              "examples": {
                "minimal": {
                  "summary": "Minimum viable bid",
                  "value": {
                    "coverLetter": "Hi, I noticed you are looking for a Next.js developer…",
                    "questionAnswerPairs": [
                      {
                        "question": "What is your rate?",
                        "answer": "USD 75/hr"
                      }
                    ]
                  }
                },
                "dryRun": {
                  "summary": "Validate without touching Upwork",
                  "value": {
                    "coverLetter": "Hi, I noticed you are looking for a Next.js developer…",
                    "questionAnswerPairs": [],
                    "dryRun": true
                  }
                },
                "withOverrides": {
                  "summary": "Fixed hourly rate and a boost ceiling",
                  "value": {
                    "coverLetter": "Hi, I noticed you are looking for a Next.js developer…",
                    "questionAnswerPairs": [],
                    "biddingConfig": {
                      "biddingHourlyRateStrategy": "fixed_rate",
                      "biddingFixedHourlyRate": 85,
                      "boostingEnabled": true,
                      "minBoost": 40,
                      "maximumBoost": 60,
                      "insufficientBoostConnectsAction": "bid_without_boost"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The bid passed validation and was queued.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LeadActionAck"
                }
              }
            }
          },
          "400": {
            "description": "The request failed validation. A question_mismatch entry carries the accepted strings in options — correct the question verbatim and retry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key, or the organization has no active paid subscription. Do not retry without a different key or a fixed billing state.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "A plan limit was reached. Not retryable until the billing period rolls over or the plan changes.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/QuotaError"
                }
              }
            }
          },
          "404": {
            "description": "The campaign or lead does not exist, or belongs to a different organization. Do not retry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "This lead was already bid on. The message names its current status. Do not retry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "The JSON body exceeded 3 MB.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/campaigns/{campaignId}/leads/{leadId}/reject": {
      "post": {
        "operationId": "rejectLead",
        "summary": "Reject a lead",
        "description": "Marks the lead as rejected so Lancer never bids on it, and closes the loop on a job.new delivery you decided to pass on. The rejection and its reason appear in the campaign’s Feed and Activity tabs.",
        "tags": [
          "Leads"
        ],
        "x-openai-isConsequential": true,
        "parameters": [
          {
            "name": "campaignId",
            "in": "path",
            "required": true,
            "description": "The campaign the lead belongs to.",
            "schema": {
              "type": "string",
              "examples": [
                "cmp_123"
              ]
            }
          },
          {
            "name": "leadId",
            "in": "path",
            "required": true,
            "description": "The lead to reject.",
            "schema": {
              "type": "string",
              "examples": [
                "lead_abc123"
              ]
            }
          }
        ],
        "requestBody": {
          "required": false,
          "description": "Optional. Records why you passed.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RejectLeadRequest"
              },
              "examples": {
                "withReason": {
                  "summary": "Reject with a reason",
                  "value": {
                    "reason": "Budget below our minimum"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The lead is rejected.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LeadActionAck"
                }
              }
            }
          },
          "400": {
            "description": "The lead was already rejected or already processed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key, or the organization has no active paid subscription. Do not retry without a different key or a fixed billing state.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "The campaign or lead does not exist, or belongs to a different organization. Do not retry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/campaigns/{campaignId}/leads/{leadId}/attachments": {
      "post": {
        "operationId": "uploadLeadAttachments",
        "summary": "Attach files to a lead’s proposal",
        "description": "Uploads files to attach to this lead’s proposal, overriding the campaign’s configured attachments for this one bid. Call it before submitBid. Up to 5 files per lead cumulatively, 25 MB each; a batch with any invalid file is rolled back whole.",
        "tags": [
          "Leads"
        ],
        "x-openai-isConsequential": true,
        "parameters": [
          {
            "name": "campaignId",
            "in": "path",
            "required": true,
            "description": "The campaign the lead belongs to.",
            "schema": {
              "type": "string",
              "examples": [
                "cmp_123"
              ]
            }
          },
          {
            "name": "leadId",
            "in": "path",
            "required": true,
            "description": "The lead whose proposal the files belong to.",
            "schema": {
              "type": "string",
              "examples": [
                "lead_abc123"
              ]
            }
          }
        ],
        "requestBody": {
          "required": true,
          "description": "multipart/form-data with one or more parts all named \"files\". Executable and script formats are rejected; duplicate file names on the same lead are rejected.",
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "title": "UploadLeadAttachmentsRequest",
                "additionalProperties": false,
                "required": [
                  "files"
                ],
                "properties": {
                  "files": {
                    "type": "array",
                    "minItems": 1,
                    "maxItems": 5,
                    "description": "Repeat the part name once per file.",
                    "items": {
                      "type": "string",
                      "contentMediaType": "application/octet-stream"
                    }
                  }
                }
              },
              "encoding": {
                "files": {
                  "contentType": "application/pdf, image/png, image/jpeg, image/gif, image/webp, application/msword, application/vnd.openxmlformats-officedocument.wordprocessingml.document, application/zip, text/plain"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The lead’s full current attachment list, including files from earlier calls.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/LeadAttachment"
                  }
                }
              }
            }
          },
          "400": {
            "description": "No file was sent, more than 5 files were sent, the cumulative limit of 5 per lead was exceeded, or a file failed type or size validation. The message names the offending file.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key, or the organization has no active paid subscription. Do not retry without a different key or a fixed billing state.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "The campaign or lead does not exist, or belongs to a different organization. Do not retry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "A file exceeded 25 MB.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "opaque",
        "description": "An organization-scoped Lancer API key, prefixed lk_ and followed by 64 hex characters."
      }
    },
    "schemas": {
      "Lead": {
        "type": "object",
        "title": "Lead",
        "description": "A job matched to one of your campaigns and persisted with a stable id, so it can be bid on or rejected. Identical to the lead object delivered on a job.new webhook, plus bidUrl and rejectUrl.",
        "additionalProperties": true,
        "required": [
          "id",
          "title",
          "jobUrl",
          "campaignId",
          "organizationId"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Stable lead id. Pass this as leadId to the bid, reject and attachment operations.",
            "examples": [
              "lead_abc123"
            ]
          },
          "createdAt": {
            "type": "integer",
            "description": "When Lancer created the lead. UNIX milliseconds."
          },
          "datetime": {
            "type": "integer",
            "description": "When the job was posted on Upwork. UNIX milliseconds."
          },
          "title": {
            "type": "string",
            "examples": [
              "Next.js developer for dashboard rebuild"
            ]
          },
          "description": {
            "type": "string",
            "description": "Full job description as posted."
          },
          "jobUrl": {
            "type": "string",
            "format": "uri",
            "examples": [
              "https://www.upwork.com/jobs/~021234567890"
            ]
          },
          "category": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "Web Development"
            ]
          },
          "occupation": {
            "type": [
              "string",
              "null"
            ]
          },
          "skills": {
            "type": "array",
            "description": "Skills tagged on the job post.",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "properties": {
                "name": {
                  "type": "string"
                }
              }
            }
          },
          "questions": {
            "type": "array",
            "description": "Screening questions. When bidding, every questionAnswerPairs[].question must be byte-identical to one of these strings.",
            "items": {
              "type": "string"
            }
          },
          "projectType": {
            "type": [
              "string",
              "null"
            ]
          },
          "projectDuration": {
            "type": [
              "string",
              "null"
            ]
          },
          "isFeatured": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "region": {
            "type": [
              "string",
              "null"
            ],
            "description": "Who Upwork allows to apply, not where the client is.",
            "enum": [
              "Worldwide",
              "USOnly",
              "UKOnly",
              null
            ]
          },
          "metadata": {
            "$ref": "#/components/schemas/LeadMetadata"
          },
          "clientInfo": {
            "$ref": "#/components/schemas/LeadClientInfo"
          },
          "attachments": {
            "type": [
              "array",
              "null"
            ],
            "description": "Files on the job post. uri is relative; prefix https://www.upwork.com.",
            "items": {
              "type": "object",
              "additionalProperties": true,
              "properties": {
                "fileName": {
                  "type": "string"
                },
                "length": {
                  "type": [
                    "integer",
                    "null"
                  ]
                },
                "uri": {
                  "type": "string"
                }
              }
            }
          },
          "campaignId": {
            "type": "string",
            "examples": [
              "cmp_123"
            ]
          },
          "campaignName": {
            "type": [
              "string",
              "null"
            ]
          },
          "organizationId": {
            "type": "string",
            "examples": [
              "org_123"
            ]
          },
          "proposalId": {
            "type": [
              "string",
              "null"
            ],
            "description": "Set once a proposal exists for this lead."
          },
          "bidUrl": {
            "type": "string",
            "format": "uri",
            "description": "Ready-to-call URL for the submitBid operation."
          },
          "rejectUrl": {
            "type": "string",
            "format": "uri",
            "description": "Ready-to-call URL for the rejectLead operation."
          }
        }
      },
      "LeadMetadata": {
        "type": "object",
        "title": "LeadMetadata",
        "description": "Job shape as posted on Upwork. Fields Upwork did not supply are null.",
        "additionalProperties": true,
        "properties": {
          "experienceLevel": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "Expert"
            ]
          },
          "paymentType": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "Hourly",
              "Fixed"
            ]
          },
          "hourlyRate": {
            "type": [
              "object",
              "null"
            ],
            "description": "Client budget range in USD per hour.",
            "additionalProperties": false,
            "properties": {
              "min": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "max": {
                "type": [
                  "number",
                  "null"
                ]
              }
            }
          },
          "fixedPrice": {
            "type": [
              "number",
              "null"
            ],
            "description": "Fixed budget in USD."
          },
          "duration": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "3 to 6 months"
            ]
          },
          "hours": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "More than 30 hrs/week"
            ]
          }
        }
      },
      "LeadClientInfo": {
        "type": "object",
        "title": "LeadClientInfo",
        "description": "The hiring client, as shown on the Upwork job post. Use totalSpent, hireRate and rating to judge whether a job is worth bidding on.",
        "additionalProperties": true,
        "properties": {
          "isPaymentVerified": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "rating": {
            "type": [
              "number",
              "null"
            ],
            "description": "0-5 average feedback score."
          },
          "numberOfReviews": {
            "type": [
              "integer",
              "null"
            ]
          },
          "country": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "United States"
            ]
          },
          "totalSpent": {
            "type": [
              "number",
              "null"
            ],
            "description": "Lifetime USD spent on Upwork."
          },
          "numberOfHires": {
            "type": [
              "integer",
              "null"
            ]
          },
          "jobsPosted": {
            "type": [
              "integer",
              "null"
            ]
          },
          "hireRate": {
            "type": [
              "number",
              "null"
            ],
            "description": "Percentage of this client’s posted jobs that ended in a hire."
          },
          "memberSince": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "2019-03-12"
            ]
          }
        }
      },
      "BiddingConfigOverride": {
        "type": "object",
        "title": "BiddingConfigOverride",
        "description": "Per-bid overrides. Every field is optional; anything omitted falls back to the lead’s and then the campaign’s configuration. Sending an unknown field is rejected.",
        "additionalProperties": false,
        "properties": {
          "bidWithWarning": {
            "type": "string",
            "enum": [
              "bid",
              "skip"
            ],
            "description": "What to do when Upwork shows a warning on the job before submitting."
          },
          "biddingHourlyRateStrategy": {
            "type": "string",
            "enum": [
              "match_job_budget",
              "match_profile_rate",
              "fixed_rate",
              "smart_bidding"
            ],
            "description": "How the proposed hourly rate is chosen."
          },
          "biddingHourlyRatePercentage": {
            "type": [
              "number",
              "null"
            ],
            "description": "Percent of the reference rate. Expected range 0-100. Required when the strategy is match_job_budget or match_profile_rate."
          },
          "biddingFixedHourlyRate": {
            "type": [
              "number",
              "null"
            ],
            "description": "USD per hour. Required when the strategy is fixed_rate."
          },
          "biddingMinHourlyRate": {
            "type": [
              "number",
              "null"
            ],
            "description": "USD per hour floor for smart_bidding. The computed rate never goes below it."
          },
          "boostingEnabled": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "Whether to boost this proposal."
          },
          "boostDownToNthPlace": {
            "type": [
              "number",
              "null"
            ],
            "description": "Target boosted position in the proposal ranking. Expected range 1-4."
          },
          "connectsAbovePrevious": {
            "type": [
              "number",
              "null"
            ],
            "description": "Outbid the previous top bid by this many connects."
          },
          "minBoost": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "description": "Minimum boost in Upwork CONNECTS, not dollars. One connect costs USD 0.15."
          },
          "maximumBoost": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "description": "Maximum boost in Upwork CONNECTS, not dollars. One connect costs USD 0.15."
          },
          "insufficientBoostConnectsAction": {
            "type": "string",
            "enum": [
              "skip",
              "bid_without_boost"
            ],
            "description": "What to do when the connected account lacks the connects to boost."
          },
          "alreadyHiredAction": {
            "type": "string",
            "enum": [
              "skip",
              "bid"
            ],
            "description": "What to do when the client has already hired someone for this job."
          }
        }
      },
      "LeadActionAck": {
        "type": "object",
        "title": "LeadActionAck",
        "additionalProperties": true,
        "required": [
          "success",
          "leadId",
          "campaignId",
          "organizationId"
        ],
        "properties": {
          "success": {
            "type": "boolean"
          },
          "leadId": {
            "type": "string",
            "examples": [
              "lead_abc123"
            ]
          },
          "campaignId": {
            "type": "string",
            "examples": [
              "cmp_123"
            ]
          },
          "organizationId": {
            "type": "string",
            "examples": [
              "org_123"
            ]
          },
          "dryRun": {
            "type": "boolean",
            "description": "Present and true when the request was a dry run."
          }
        }
      },
      "Error": {
        "type": "object",
        "title": "Error",
        "description": "The standard error body. Read `message` for the human-readable reason.",
        "additionalProperties": true,
        "required": [
          "statusCode",
          "message"
        ],
        "properties": {
          "statusCode": {
            "type": "integer",
            "examples": [
              404
            ]
          },
          "message": {
            "type": "string",
            "examples": [
              "Lead not found"
            ]
          },
          "error": {
            "type": "string",
            "examples": [
              "Not Found"
            ]
          }
        }
      },
      "ValidationError": {
        "type": "object",
        "title": "ValidationError",
        "description": "Domain validation failure. Each entry points at a field in your request body. When code is question_mismatch, `options` holds the exact accepted strings, so an integration can correct itself and retry.",
        "additionalProperties": true,
        "required": [
          "message",
          "errors"
        ],
        "properties": {
          "message": {
            "type": "string",
            "examples": [
              "Validation failed"
            ]
          },
          "errors": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true,
              "required": [
                "path",
                "code",
                "message"
              ],
              "properties": {
                "path": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  },
                  "description": "Path into the request body.",
                  "examples": [
                    [
                      "questionAnswerPairs",
                      "0",
                      "question"
                    ]
                  ]
                },
                "code": {
                  "type": "string",
                  "enum": [
                    "question_mismatch",
                    "rate_percentage_required",
                    "fixed_rate_required"
                  ]
                },
                "message": {
                  "type": "string"
                },
                "options": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  },
                  "description": "Accepted values, when the failure has a closed set of them."
                }
              }
            }
          }
        }
      },
      "QuotaError": {
        "type": "object",
        "title": "QuotaError",
        "description": "A plan limit was reached. Not retryable until the period rolls over or the plan changes.",
        "additionalProperties": true,
        "required": [
          "message",
          "code"
        ],
        "properties": {
          "message": {
            "type": "string",
            "examples": [
              "Proposal limit reached. Usage resets on Aug 1, 2026."
            ]
          },
          "code": {
            "type": "string",
            "description": "On this API the reachable value is proposal_limit_reached. The subscription codes surface on management operations, which this document does not cover.",
            "enum": [
              "proposal_limit_reached"
            ]
          },
          "currentUsage": {
            "type": "integer",
            "examples": [
              500
            ]
          },
          "limit": {
            "type": "integer",
            "examples": [
              500
            ]
          },
          "currentPeriodEnd": {
            "type": "integer",
            "description": "UNIX seconds.",
            "examples": [
              1754006400
            ]
          }
        }
      },
      "LeadAttachment": {
        "type": "object",
        "title": "LeadAttachment",
        "additionalProperties": true,
        "required": [
          "id",
          "originalName",
          "contentType",
          "sizeBytes"
        ],
        "properties": {
          "id": {
            "type": "string",
            "examples": [
              "att_123"
            ]
          },
          "originalName": {
            "type": "string",
            "examples": [
              "portfolio.pdf"
            ]
          },
          "safeName": {
            "type": "string",
            "examples": [
              "portfolio.pdf"
            ]
          },
          "contentType": {
            "type": "string",
            "examples": [
              "application/pdf"
            ]
          },
          "sizeBytes": {
            "type": "integer",
            "examples": [
              1048576
            ]
          },
          "storagePath": {
            "type": "string"
          },
          "sha256": {
            "type": "string"
          },
          "createdAt": {
            "type": "integer",
            "description": "UNIX milliseconds."
          }
        }
      },
      "QuestionAnswerPair": {
        "type": "object",
        "title": "QuestionAnswerPair",
        "additionalProperties": false,
        "required": [
          "question",
          "answer"
        ],
        "properties": {
          "question": {
            "type": "string",
            "description": "Byte-identical copy of one string from the lead’s questions array. Whitespace, punctuation and casing all count."
          },
          "answer": {
            "type": "string",
            "description": "Your answer, submitted as written."
          }
        }
      },
      "SubmitBidRequest": {
        "type": "object",
        "title": "SubmitBidRequest",
        "additionalProperties": false,
        "required": [
          "coverLetter",
          "questionAnswerPairs"
        ],
        "properties": {
          "coverLetter": {
            "type": "string",
            "description": "The proposal body, submitted as-is."
          },
          "questionAnswerPairs": {
            "type": "array",
            "description": "One entry per screening question on the lead. Send an empty array when it has none.",
            "items": {
              "$ref": "#/components/schemas/QuestionAnswerPair"
            }
          },
          "dryRun": {
            "type": "boolean",
            "default": false,
            "description": "Validate and simulate a bid_complete webhook without spending connects, calling Upwork, or changing the lead."
          },
          "biddingConfig": {
            "$ref": "#/components/schemas/BiddingConfigOverride"
          }
        }
      },
      "RejectLeadRequest": {
        "type": "object",
        "title": "RejectLeadRequest",
        "additionalProperties": false,
        "properties": {
          "reason": {
            "type": "string",
            "maxLength": 500,
            "description": "Why you passed. Shown in the web app."
          }
        }
      }
    }
  }
}