openapi: 3.1.0 info: title: Juno Pay — API Pública version: "1.0.0" summary: Crie e consulte cobranças (PIX, Boleto e Cartão) via REST. contact: name: Juno Pay url: https://api.junopay.com.br description: | ![Juno Pay](/logo/juno-horizontal.svg) ![Juno Pay](/logo/juno-horizontal-light.svg) API REST para sistemas externos criarem e consultarem cobranças (**PIX**, **Boleto** e **Cartão de Crédito**) na sua subconta Juno Pay. ## Autenticação Toda requisição deve enviar sua chave secreta no cabeçalho `x-api-key`: ```http x-api-key: jpk_live_xxxxxxxxxxxxxxxxxxxxxxxx ``` - A chave identifica a **subconta** e o **ambiente** (`test` ou `live`). Todas as operações ficam restritas à subconta vinculada à chave. - A chave nunca é exibida novamente após a criação. Guarde-a com segurança e **nunca** a exponha em código de frontend. - Chaves possuem **escopos**. Os endpoints abaixo indicam o escopo exigido (`payments:read`, `payments:write`). Sem o escopo → `403`. ## Ambientes O ambiente é determinado pela própria chave, não pela URL: | Chave | Ambiente | Movimenta dinheiro real? | | ------ | -------- | ------------------------ | | `test` | Sandbox | Não | | `live` | Produção | Sim | Usar uma chave `live` no ambiente de testes (ou vice-versa) retorna `403`. ## Limite de requisições Até **120 requisições por minuto** por chave (melhor esforço — a aplicação distribui o tráfego entre múltiplas instâncias). Ao exceder, a API responde `429 Too Many Requests`. ## Idempotência Em `POST /payments`, envie `externalReference` com um identificador único do seu sistema. Ele é usado como **chave de idempotência**: repetir a mesma requisição retorna a cobrança já criada (`200`) em vez de duplicá-la. ## Checkout transparente A resposta de criação já traz o que você precisa para o cliente pagar **no seu próprio site**, sem redirecionar para uma página hospedada: | Forma | Campo no retorno | Conteúdo | | ----- | ---------------- | -------- | | **PIX** | `pix.encodedImage` / `pix.payload` | QR Code (PNG base64) e copia-e-cola | | **Boleto** | `boleto.identificationField` + `bankSlipUrl` | linha digitável e PDF | | **Cartão** | `creditCard` + `status` | bandeira/últimos 4/token e a autorização (`CONFIRMED`/`RECEIVED` quando aprovado) | Para o cartão, envie `creditCard` + `creditCardHolderInfo` + `remoteIp` (IP do pagador), **ou** um `creditCardToken` salvo. O `customer` é resolvido na adquirente pelo `cpfCnpj` (reaproveitado se já existir). Em `GET /payments/{id}`, cobranças PIX/Boleto ainda pendentes também retornam `pix`/`boleto` — útil para uma tela de status que faz polling. ## ⚠️ Segurança / PCI - A chave (`x-api-key`) é secreta: use **somente no backend**. Nunca a coloque em código de navegador. - No cartão cru, o número/CVV trafegam pelo seu servidor → nossa API → adquirente (sempre por TLS). **Nunca** armazenamos PAN/CVV; devolvemos apenas bandeira, últimos 4 dígitos e um `token` reutilizável. Isso coloca seu backend no escopo **PCI DSS (SAQ A-EP)** — trate os dados do cartão com cuidado e prefira `creditCardToken` para recorrência. ## Webhooks de saída Quando o status de uma cobrança muda, a Juno Pay envia um `POST` assinado para a URL de webhook cadastrada na subconta. Veja a seção **Webhooks**. servers: - url: https://api.junopay.com.br description: Produção security: - ApiKeyAuth: [] tags: - name: Cobranças description: Criação e consulta de cobranças. paths: /payments: post: tags: [Cobranças] summary: Criar cobrança operationId: createPayment description: | Cria uma cobrança na subconta vinculada à chave. Exige o escopo `payments:write`. security: - ApiKeyAuth: [] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CreatePaymentRequest' examples: pix: summary: Cobrança PIX value: value: 149.90 billingType: PIX dueDate: "2026-07-15" customer: name: Maria Souza cpfCnpj: "12345678909" email: maria@exemplo.com.br description: Plano mensal externalReference: pedido-2026-0001 boleto: summary: Boleto parcelado value: value: 600.00 billingType: BOLETO dueDate: "2026-07-20" installmentCount: 3 customer: name: João Lima cpfCnpj: "11222333000181" cartao: summary: Cartão de crédito transparente value: value: 199.90 billingType: CREDIT_CARD dueDate: "2026-07-15" remoteIp: "200.182.0.10" customer: name: João Silva cpfCnpj: "12345678909" email: joao@email.com creditCard: holderName: JOAO SILVA number: "5162306219378829" expiryMonth: "05" expiryYear: "2028" ccv: "318" creditCardHolderInfo: name: João Silva email: joao@email.com cpfCnpj: "12345678909" postalCode: "01001000" addressNumber: "123" cartaoToken: summary: Cartão com token salvo (recorrência) value: value: 49.90 billingType: CREDIT_CARD dueDate: "2026-07-15" customer: name: João Silva cpfCnpj: "12345678909" creditCardToken: a1b2c3d4-token-exemplo responses: '201': description: Cobrança criada. content: application/json: schema: $ref: '#/components/schemas/PaymentCreated' '200': description: | Cobrança já existente (idempotência por `externalReference`). Retorna os campos base da cobrança — `pix`, `boleto` e `bankSlipUrl` não são incluídos; use `GET /payments/{id}` para obter esses dados. content: application/json: schema: $ref: '#/components/schemas/Payment' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '422': $ref: '#/components/responses/UnprocessableEntity' '429': $ref: '#/components/responses/TooManyRequests' '500': $ref: '#/components/responses/InternalError' /payments/{id}: get: tags: [Cobranças] summary: Consultar cobrança operationId: getPayment description: | Retorna uma cobrança pelo `id` interno da Juno Pay. Exige o escopo `payments:read`. security: - ApiKeyAuth: [] parameters: - name: id in: path required: true description: ID interno da cobrança na Juno Pay. schema: type: string format: uuid responses: '200': description: Cobrança encontrada. content: application/json: schema: $ref: '#/components/schemas/Payment' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' '429': $ref: '#/components/responses/TooManyRequests' '500': $ref: '#/components/responses/InternalError' webhooks: paymentEvent: post: summary: Evento de cobrança operationId: paymentWebhook description: | Enviado pela Juno Pay para a **URL de webhook cadastrada na subconta** sempre que o status de uma cobrança muda. ### Verificação da assinatura Cada entrega inclui os cabeçalhos: | Cabeçalho | Descrição | | ------------------ | ------------------------------------------------------ | | `X-Juno-Event` | Nome do evento (ex.: `PAYMENT_RECEIVED`). | | `X-Juno-Signature` | `sha256=` — HMAC-SHA256 do **corpo bruto** da requisição, usando o segredo do webhook. | Recalcule o HMAC sobre o corpo recebido e compare (comparação em tempo constante) com o valor após `sha256=`. Descarte entregas que não batem. ### Reentregas Em falha de rede, `5xx` ou `429`, a Juno Pay tenta novamente (até 3 tentativas, com backoff). Erros `4xx` terminais (exceto `429`) não são reenviados. Responda `2xx` para confirmar o recebimento. parameters: - name: X-Juno-Event in: header required: true schema: $ref: '#/components/schemas/PaymentEventName' - name: X-Juno-Signature in: header required: true description: '`sha256=` — assinatura HMAC-SHA256 do corpo.' schema: type: string example: sha256=9b2c1f0a8e7d6c5b4a3f2e1d0c9b8a7f6e5d4c3b2a190817263544536271809a requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/WebhookEvent' example: event: PAYMENT_RECEIVED payment: id: pay_8a1b2c3d4e5f status: RECEIVED value: 149.90 billingType: PIX dueDate: "2026-07-15" externalReference: pedido-2026-0001 responses: '200': description: Recebido. Qualquer `2xx` confirma a entrega. components: securitySchemes: ApiKeyAuth: type: apiKey in: header name: x-api-key description: Chave secreta da subconta. Enviada no cabeçalho `x-api-key`. schemas: PaymentStatus: type: string description: Status normalizado da cobrança. enum: - PENDING - CONFIRMED - RECEIVED - OVERDUE - REFUNDED - CANCELLED BillingType: type: string description: Forma de pagamento. enum: - PIX - BOLETO - CREDIT_CARD Customer: type: object required: [name, cpfCnpj] properties: name: type: string description: Nome do cliente. example: Maria Souza cpfCnpj: type: string description: CPF ou CNPJ (somente dígitos). example: "12345678909" email: type: string format: email description: E-mail do cliente (opcional). example: maria@exemplo.com.br phone: type: string description: Telefone do cliente (opcional). example: "11999998888" CreatePaymentRequest: type: object required: [value, billingType, dueDate, customer] properties: value: type: number format: float exclusiveMinimum: 0 description: | Valor **total** da cobrança em reais (BRL). Quando `installmentCount > 1`, este total é dividido entre as parcelas pela adquirente. example: 149.90 billingType: $ref: '#/components/schemas/BillingType' dueDate: type: string format: date pattern: '^\d{4}-\d{2}-\d{2}$' description: Data de vencimento no formato `YYYY-MM-DD`. example: "2026-07-15" customer: $ref: '#/components/schemas/Customer' description: type: string description: Descrição livre exibida ao cliente (opcional). example: Plano mensal externalReference: type: string description: | Identificador do seu sistema. Também usado como **chave de idempotência** (opcional). example: pedido-2026-0001 installmentCount: type: integer minimum: 1 maximum: 24 description: | Número de parcelas (opcional; padrão 1). Apenas `BOLETO` e `CREDIT_CARD` — `PIX` não é parcelável. example: 3 creditCard: $ref: '#/components/schemas/CreditCard' creditCardHolderInfo: $ref: '#/components/schemas/CreditCardHolderInfo' creditCardToken: type: string description: | Token de cartão salvo (alternativa ao `creditCard` cru). Quando presente, dispensa `creditCard`/`creditCardHolderInfo`. remoteIp: type: string description: | IP do pagador — **obrigatório** no cartão cru e **recomendado** no cartão tokenizado (melhora a análise de risco da adquirente). Ignorado em PIX/Boleto. example: "200.182.0.10" CreditCard: type: object description: Dados do cartão (checkout transparente). Use apenas no backend. required: [holderName, number, expiryMonth, expiryYear, ccv] properties: holderName: { type: string, example: JOAO SILVA } number: { type: string, description: Número do cartão (somente dígitos)., example: "5162306219378829" } expiryMonth: { type: string, example: "05" } expiryYear: { type: string, example: "2028" } ccv: { type: string, example: "318" } CreditCardHolderInfo: type: object description: Dados do titular do cartão (exigidos no cartão cru). required: [name, email, cpfCnpj, postalCode, addressNumber] properties: name: { type: string, example: João Silva } email: { type: string, format: email, example: joao@email.com } cpfCnpj: { type: string, example: "12345678909" } postalCode: { type: string, example: "01001000" } addressNumber: { type: string, example: "123" } addressComplement: { type: string, example: Apto 12 } phone: { type: string, example: "1133334444" } mobilePhone: { type: string, example: "11999998888" } Pix: type: object description: Dados para pagamento PIX transparente. properties: encodedImage: { type: string, description: PNG do QR Code em base64. } payload: { type: string, description: Copia-e-cola (EMV). } expirationDate: { type: string, example: "2026-07-15 23:59:59" } Boleto: type: object description: Dados para pagamento de boleto transparente. properties: identificationField: { type: string, description: Linha digitável. } barCode: { type: string, description: Código de barras. } nossoNumero: { type: string } CardSummary: type: object description: Resumo do cartão autorizado (sem PAN/CVV). properties: brand: { type: string, example: MASTERCARD } last4: { type: string, description: Últimos 4 dígitos., example: "8829" } token: { type: string, description: Token reutilizável para recorrência. } Payment: type: object required: [id, status, value, billingType, dueDate, asaasId, createdAt] properties: id: type: string format: uuid description: ID interno da cobrança na Juno Pay. status: $ref: '#/components/schemas/PaymentStatus' value: type: number format: float example: 149.90 billingType: $ref: '#/components/schemas/BillingType' dueDate: type: string format: date example: "2026-07-15" externalReference: type: [string, 'null'] example: pedido-2026-0001 asaasId: type: string description: ID da cobrança na adquirente. example: pay_8a1b2c3d4e5f createdAt: type: string format: date-time example: "2026-06-22T13:45:00.000Z" pix: description: Presente em PIX pendente (QR + copia-e-cola). oneOf: - $ref: '#/components/schemas/Pix' - type: 'null' boleto: description: Presente em Boleto pendente (linha digitável). oneOf: - $ref: '#/components/schemas/Boleto' - type: 'null' bankSlipUrl: type: string format: uri description: Link do boleto (PDF), em cobranças de boleto. PaymentCreated: description: | Retorno do `201`. Inclui os dados de checkout transparente e os links gerados pela adquirente. allOf: - $ref: '#/components/schemas/Payment' - type: object properties: invoiceUrl: type: string format: uri description: Link da fatura/checkout hospedado (alternativa ao transparente). example: https://www.asaas.com/i/8a1b2c3d4e5f bankSlipUrl: type: string format: uri description: Link do boleto (quando `billingType = BOLETO`). example: https://www.asaas.com/b/8a1b2c3d4e5f pix: $ref: '#/components/schemas/Pix' boleto: $ref: '#/components/schemas/Boleto' creditCard: $ref: '#/components/schemas/CardSummary' PaymentEventName: type: string description: Nome do evento de webhook. enum: - PAYMENT_CREATED - PAYMENT_CONFIRMED - PAYMENT_RECEIVED - PAYMENT_OVERDUE - PAYMENT_DELETED - PAYMENT_RESTORED - PAYMENT_REFUNDED - PAYMENT_PARTIALLY_REFUNDED WebhookEvent: type: object required: [event, payment] properties: event: $ref: '#/components/schemas/PaymentEventName' payment: type: object description: Dados da cobrança no momento do evento. properties: id: type: string description: ID da cobrança na adquirente. status: type: string value: type: number billingType: type: string dueDate: type: string externalReference: type: [string, 'null'] Error: type: object required: [error] properties: error: type: string description: Mensagem de erro legível. responses: BadRequest: description: Corpo JSON inválido. content: application/json: schema: $ref: '#/components/schemas/Error' example: error: Corpo JSON inválido. Unauthorized: description: Chave ausente, inválida ou revogada. content: application/json: schema: $ref: '#/components/schemas/Error' examples: missing: value: error: Missing x-api-key header. invalid: value: error: Invalid API key. revoked: value: error: API key revoked. Forbidden: description: Escopo insuficiente ou chave de ambiente incorreto. content: application/json: schema: $ref: '#/components/schemas/Error' examples: scope: value: error: 'Missing required scope: payments:write' environment: value: error: Chave de produção não pode operar no ambiente de testes. NotFound: description: Cobrança não encontrada na subconta. content: application/json: schema: $ref: '#/components/schemas/Error' example: error: Cobrança não encontrada. UnprocessableEntity: description: | Falha de validação ou subconta inelegível (inativa, não provisionada, ou cobrança rejeitada pela adquirente). content: application/json: schema: $ref: '#/components/schemas/Error' examples: value: value: error: value deve ser um número maior que zero. billingType: value: error: 'billingType deve ser um de: PIX, BOLETO, CREDIT_CARD.' dueDate: value: error: dueDate deve estar no formato YYYY-MM-DD. subaccount: value: error: Subconta inativa ou não aprovada para receber cobranças. rejected: value: error: Pagamento rejeitado pela adquirente. Verifique os dados da cobrança. TooManyRequests: description: Limite de 120 requisições/minuto excedido. content: application/json: schema: $ref: '#/components/schemas/Error' example: error: Too many requests — tente novamente em instantes. InternalError: description: Erro interno. content: application/json: schema: $ref: '#/components/schemas/Error' example: error: Erro interno.