{
  "openapi": "3.1.0",
  "info": {
    "title": "Jobreach agent API",
    "version": "1.1.0",
    "summary": "Send Jobreach the hiring posts your own agent finds, with a personal key.",
    "description": "A person makes a key under Profile, Your agent, on https://jobreach.app and gives it to their own agent. Each post sent becomes that person's private lead: Jobreach reads it, checks the company, scores it against their own work, and for a strong or good match prepares a one-page resume and an email to a published address, as a draft in their Gmail. The person reads it and presses Send. Do not extract addresses, verify companies, write emails or create drafts yourself: Jobreach does that. Guide: https://jobreach.app/agents An agent can also ask for a key itself, with a six-digit code the person types on jobreach.app (the auth.md protocol): https://jobreach.app/auth.md",
    "contact": {
      "email": "support@jobreach.app",
      "url": "https://jobreach.app/agents"
    }
  },
  "servers": [
    {
      "url": "https://jobreach.app/api"
    }
  ],
  "security": [
    {
      "agentKey": []
    }
  ],
  "components": {
    "securitySchemes": {
      "agentKey": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "jri_...",
        "description": "A personal key: made by the person under Profile, Your agent, on jobreach.app, or got by an agent through /agent/identity and /oauth2/token after the person typed the code it showed them. The person can turn it off at any time."
      }
    },
    "schemas": {
      "Lead": {
        "type": "object",
        "required": [
          "text"
        ],
        "properties": {
          "text": {
            "type": "string",
            "minLength": 40,
            "maxLength": 40000,
            "description": "The whole post exactly as shown, to the end, including the poster's line and its age."
          },
          "url": {
            "type": "string",
            "description": "The post's link. The same link sent twice is one lead."
          },
          "age_label": {
            "type": "string",
            "description": "The post's age as shown, like \"45m\", \"3h\" or \"2d\"."
          },
          "found_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the agent saw the post."
          },
          "found_by": {
            "type": "string",
            "description": "The search that found it."
          }
        }
      }
    }
  },
  "paths": {
    "/intake/leads": {
      "post": {
        "summary": "Send hiring posts",
        "description": "Up to 25 posts per call and 100 per day. Each becomes the key owner's private lead.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "leads"
                ],
                "properties": {
                  "leads": {
                    "type": "array",
                    "maxItems": 25,
                    "items": {
                      "$ref": "#/components/schemas/Lead"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Received. Each result is queued, or duplicate when the same post is already one of the person's leads.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "received": {
                      "type": "integer"
                    },
                    "queued": {
                      "type": "integer"
                    },
                    "results": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "job_id": {
                            "type": "string"
                          },
                          "status": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "The key is wrong or turned off."
          },
          "422": {
            "description": "A post is missing its text or is too short."
          },
          "429": {
            "description": "Today's limit is reached."
          }
        }
      }
    },
    "/intake/me": {
      "get": {
        "summary": "Whose key this is, and today's emails",
        "responses": {
          "200": {
            "description": "The key owner's name and email, the key's scope, and how many emails Jobreach has written for them today out of the daily number.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "name": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "email": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "scope": {
                      "type": "string"
                    },
                    "letters_today": {
                      "type": "integer"
                    },
                    "letters_per_day": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "The key is wrong or turned off."
          }
        }
      }
    },
    "/intake/key": {
      "delete": {
        "summary": "Turn this key off",
        "description": "The key that sends this request stops working at once.",
        "responses": {
          "200": {
            "description": "Turned off."
          },
          "401": {
            "description": "The key is wrong or already off."
          }
        }
      }
    },
    "/agent/identity": {
      "post": {
        "summary": "Ask for a key for a person (auth.md, service_auth)",
        "security": [],
        "description": "Answered with a claim token, a six-digit code and a link for the person. Jobreach emails nobody. At most 5 requests an hour from one address. See https://jobreach.app/auth.md.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "type",
                  "login_hint"
                ],
                "properties": {
                  "type": {
                    "type": "string",
                    "enum": [
                      "service_auth"
                    ]
                  },
                  "login_hint": {
                    "type": "string",
                    "description": "The email the person signs in to Jobreach with."
                  },
                  "agent_name": {
                    "type": "string",
                    "maxLength": 60,
                    "description": "Shown to the person as your own words."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The claim ceremony: claim_token, claim.user_code, claim.verification_uri, claim.expires_in (600), claim.interval (5)."
          },
          "400": {
            "description": "invalid_request, anonymous_not_enabled or identity_assertion_not_enabled."
          },
          "429": {
            "description": "rate_limited."
          }
        }
      }
    },
    "/agent/identity/claim": {
      "post": {
        "summary": "A fresh code for a request still waiting",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "claim_token"
                ],
                "properties": {
                  "claim_token": {
                    "type": "string"
                  },
                  "email": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "claim_attempt: a new user_code and verification_uri; the previous link stops working."
          },
          "400": {
            "description": "invalid_claim_token, claimed_or_in_flight, claim_expired or invalid_request."
          }
        }
      }
    },
    "/oauth2/token": {
      "post": {
        "summary": "Poll for the key, or trade the assertion for a new one",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "type": "object",
                "required": [
                  "grant_type"
                ],
                "properties": {
                  "grant_type": {
                    "type": "string",
                    "enum": [
                      "urn:workos:agent-auth:grant-type:claim",
                      "urn:ietf:params:oauth:grant-type:jwt-bearer"
                    ]
                  },
                  "claim_token": {
                    "type": "string"
                  },
                  "assertion": {
                    "type": "string"
                  }
                }
              }
            },
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "grant_type"
                ],
                "properties": {
                  "grant_type": {
                    "type": "string",
                    "enum": [
                      "urn:workos:agent-auth:grant-type:claim",
                      "urn:ietf:params:oauth:grant-type:jwt-bearer"
                    ]
                  },
                  "claim_token": {
                    "type": "string"
                  },
                  "assertion": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "access_token (a jri_ key), token_type Bearer, scope intake; the claim grant adds identity_assertion and assertion_expires."
          },
          "400": {
            "description": "authorization_pending, slow_down, access_denied, expired_token, invalid_grant or unsupported_grant_type (RFC 6749 error body)."
          }
        }
      }
    },
    "/oauth2/revoke": {
      "post": {
        "summary": "Turn a key off (RFC 7009)",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "type": "object",
                "required": [
                  "token"
                ],
                "properties": {
                  "token": {
                    "type": "string"
                  }
                }
              }
            },
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "token"
                ],
                "properties": {
                  "token": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Always, known token or not. The connection ends with it."
          }
        }
      }
    }
  }
}
